/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.apache.geode.cache;

import static org.apache.geode.internal.cache.DiskStoreAttributes.verifyNonNegativeDirSize;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.geode.GemFireIOException;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.compression.Compressor;
import org.apache.geode.internal.cache.AbstractRegion;
import org.apache.geode.internal.cache.DiskStoreFactoryImpl;
import org.apache.geode.internal.cache.DiskWriteAttributesImpl;
import org.apache.geode.internal.cache.EvictionAttributesImpl;
import org.apache.geode.internal.cache.PartitionAttributesImpl;
import org.apache.geode.internal.cache.PartitionedRegionHelper;
import org.apache.geode.internal.cache.UserSpecifiedRegionAttributes;
import org.apache.geode.internal.cache.persistence.DefaultDiskDirs;
import org.apache.geode.internal.cache.xmlcache.RegionAttributesCreation;

/**
 * Creates instances of {@link RegionAttributes}. An {@code AttributesFactory} instance maintains
 * state for creating {@code RegionAttributes} instances. The setter methods are used to change the
 * settings that will be used for creating the next attributes instance with the {@link #create}
 * method. If you create a factory with the default constructor, then the factory is set up to
 * create attributes with all default settings. You can also create a factory by providing a
 * {@code RegionAttributes}, which will set up the new factory with the settings provided in that
 * attributes instance.
 *
 * <p>
 * Once a {@code RegionAttributes} is created, it can only be modified after it has been used to
 * create a {@code Region}, and then only by using an {@link AttributesMutator} obtained from the
 * region.
 *
 * <h3>Attributes</h3>
 * <h4>Callbacks</h4>
 * <dl>
 * <dt>{@link CacheLoader} [<em>default:</em> {@code null}, meaning no loader]</dt>
 * <dd>User-implemented plug-in for loading data on cache misses.<br>
 * {@link #setCacheLoader} {@link RegionAttributes#getCacheLoader}
 * {@link AttributesMutator#setCacheLoader}</dd>
 *
 * <dt>{@link CacheWriter} [<em>default:</em> {@code null}, meaning no writer]</dt>
 * <dd>User-implemented plug-in for intercepting cache modifications, e.g. for writing to an
 * external data source.<br>
 * {@link #setCacheWriter} {@link RegionAttributes#getCacheWriter}
 * {@link AttributesMutator#setCacheWriter}</dd>
 *
 * <dt>{@link CacheListener} [<em>default:</em> {@code null}, meaning no listener ]</dt>
 * <dd>User-implemented plug-in for receiving and handling cache related events.<br>
 * {@link #addCacheListener} {@link #initCacheListeners} {@link #initCacheListeners}
 * {@link RegionAttributes#getCacheListeners} {@link AttributesMutator#initCacheListeners}
 * {@link AttributesMutator#addCacheListener} {@link AttributesMutator#removeCacheListener}</dd>
 * </dl>
 * <h4>Expiration</h4>
 * <dl>
 * <dt>RegionTimeToLive [<em>default:</em> no expiration]</dt>
 * <dd>Expiration configuration for the entire region based on the
 * {@link CacheStatistics#getLastModifiedTime lastModifiedTime}.<br>
 * {@link #setRegionTimeToLive} {@link RegionAttributes#getRegionTimeToLive}
 * {@link AttributesMutator#setRegionTimeToLive}</dd>
 *
 * <dt>RegionIdleTimeout [<em>default:</em> no expiration]</dt>
 * <dd>Expiration configuration for the entire region based on the
 * {@link CacheStatistics#getLastAccessedTime lastAccessedTime}.<br>
 * {@link #setRegionIdleTimeout} {@link RegionAttributes#getRegionIdleTimeout}
 * {@link AttributesMutator#setRegionIdleTimeout}</dd>
 *
 * <dt>EntryTimeToLive [<em>default:</em> no expiration]</dt>
 * <dd>Expiration configuration for individual entries based on the
 * {@link CacheStatistics#getLastModifiedTime lastModifiedTime}.<br>
 * {@link #setEntryTimeToLive} {@link RegionAttributes#getEntryTimeToLive}
 * {@link AttributesMutator#setEntryTimeToLive}</dd>
 *
 * <dt>EntryIdleTimeout [<em>default:</em> no expiration]</dt>
 * <dd>Expiration configuration for individual entries based on the
 * {@link CacheStatistics#getLastAccessedTime lastAccessedTime}.<br>
 * {@link #setEntryIdleTimeout} {@link RegionAttributes#getEntryIdleTimeout}
 * {@link AttributesMutator#setEntryIdleTimeout}</dd>
 * </dl>
 * <h4>Distribution</h4>
 * <dl>
 * <dt>{@link Scope}[<em>default:</em> {@link Scope#DISTRIBUTED_NO_ACK}]</dt>
 * <dd>Properties of distribution for the region, including whether it is distributed at all,
 * whether acknowledgements are required, and whether distributed synchronization is required. <br>
 * {@link #setScope} {@link RegionAttributes#getScope}</dd>
 *
 * <dt>EarlyAck [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not acks required by {@code Scope.DISTRIBUTED_ACK} are sent after an operation is
 * processed. If {@code true} then remote caches will ACK before processing an operation sent by the
 * cache that has set earlyAck to {@code true}. Note that this attribute is only meaningful on the
 * cache that is initiating an operation; it does not matter what it is set to on the cache that
 * receives the operation.<br>
 * {@link #setEarlyAck} {@link RegionAttributes#getEarlyAck}</dd>
 *
 * <dt>{@link SubscriptionAttributes} [<em>default:</em> {@link InterestPolicy#DEFAULT}]</dt>
 * <dd>How will the region in this cache subscribe to other distributed instances of this region.
 * <br>
 * {@link #setSubscriptionAttributes} {@link RegionAttributes#getSubscriptionAttributes}</dd>
 *
 * <dt>EnableAsyncConflation [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not conflation is enabled for sending messages to async peers. Async peers are
 * those whose {@code async-distribution-timeout} gemfire.property is greater than zero.
 * AsyncConflation is ignored if the scope is {@code DISTRIBUTED_ACK} or {@code GLOBAL}. Conflation
 * is only done on entry update operations. It is done by dropping the earlier update from the
 * message queue. {@link #setEnableAsyncConflation}
 * {@link RegionAttributes#getEnableAsyncConflation}</dd>
 * <dt>poolName [<em>default:</em> {@code null}, meaning no pool]</dt>
 * <dd>Whether or not this region is a client that is to use connections from the named pool to
 * communicate with servers. If {@code null}, then it is not a client. If {@code non-null}, then the
 * named pool will be used. {@link #setPoolName} {@link RegionAttributes#getPoolName}</dd>
 *
 *
 * <dt>EnableSubscriptionConflation [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not conflation is enabled for sending messages from a cache server to its clients.
 * Note: This parameter is only valid for cache server to client communication. It has no effect in
 * peer to peer communication. If {@code true}, messages will be conflated before they are sent from
 * a cache server to its clients. Only the latest value will be sent. Note that this attribute is
 * only meaningful in a client server topology. {@link #setEnableSubscriptionConflation}
 * {@link RegionAttributes#getEnableSubscriptionConflation}</dd>
 * <dt>Publisher [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not a region is a publisher. Publishers are regions that will have distributed
 * write operations done on them. If a publisher is also a replicate then it will be used as the
 * preferred source for initializing other replicates. {@link #setPublisher}
 * {@link RegionAttributes#getPublisher}</dd>
 * <dt>isCloningEnabled [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not value is cloned before appling {@code Delta}s If {@code false}, value will not
 * be cloned {@link #setCloningEnabled} {@link RegionAttributes#getCloningEnabled()}</dd>
 * </dl>
 * <h4>Storage (see also <a href="package-summary.html#storage">package summary </a>)</h4>
 * <dl>
 * <dt>{@link DataPolicy} [<em>default:</em> {@code DataPolicy.NORMAL}]</dt>
 * <dd>Specifies the data storage policy.<br>
 * {@link #setDataPolicy} {@link RegionAttributes#getDataPolicy}</dd>
 *
 * <dt>{@link MirrorType} [<em>default:</em> {@code MirrorType.NONE}]</dt>
 * <dd><em>Deprecated</em>, use DataPolicy instead.</dd>
 *
 * <dt>{@link #setEvictionAttributes(EvictionAttributes) EvictionAttributes}</dt>
 * <dd>{@link EvictionAttributes} are the replacement for the deprecated and removed
 * CapacityController interface. EvictionAttributes describe the {@link EvictionAlgorithm} and the
 * {@link EvictionAction} as well as the various conditions under which the algorithm perform the
 * action e.g. when the maximum number of entries has been reached or the maximum percentage of JVM
 * heap has been consumed. Setting {@code EvictionAttributes} installs an eviction controller on the
 * Region instantiated with the associated RegionAttributes</dd>
 *
 * <dt>KeyConstraint [<em>default:</em> {@code null}, meaning no constraint]</dt>
 * <dd>The Class to constrain the keys to in the region.<br>
 * {@link #setKeyConstraint} {@link RegionAttributes#getKeyConstraint}</dd>
 *
 * <dt>ValueConstraint [<em>default:</em> {@code null}, meaning no constraint]</dt>
 * <dd>The Class to constrain the values to in the region. In addition to the utility of this for
 * applications in general, a {@code valueConstraint} is helpful for compiling queries.<br>
 * {@link #setValueConstraint} {@link RegionAttributes#getValueConstraint}</dd>
 *
 * <dt>InitialCapacity [<em>default:</em> {@code 16}]</dt>
 * <dd>The initial capacity of the map used for storing the entries. <br>
 * {@link java.util.HashMap} {@link #setInitialCapacity}
 * {@link RegionAttributes#getInitialCapacity}</dd>
 *
 * <dt>LoadFactor [<em>default:</em> {@code 0.75}]</dt>
 * <dd>The load factor of the map used for storing the entries. <br>
 * {@link java.util.HashMap} {@link #setLoadFactor} {@link RegionAttributes#getLoadFactor}</dd>
 *
 * <dt>ConcurrencyLevel [<em>default:</em> {@code 16}]</dt>
 * <dd>The allowed concurrency among updates to values in the region is guided by the
 * <tt>concurrencyLevel</tt>, which is used as a hint for internal sizing. The actual concurrency
 * will vary. Ideally, you should choose a value to accommodate as many threads as will ever
 * concurrently modify values in the region. Using a significantly higher value than you need can
 * waste space and time, and a significantly lower value can lead to thread contention. But
 * overestimates and underestimates within an order of magnitude do not usually have much noticeable
 * impact. A value of one is appropriate when it is known that only one thread will modify and all
 * others will only read. <br>
 * {@link #setConcurrencyLevel} {@link RegionAttributes#getConcurrencyLevel}</dd>
 *
 * <dt>ConcurrencyChecksEnabled [<em>default:</em> {@code true}]</dt>
 * <dd>Enables a distributed versioning algorithm that detects concurrency conflicts in regions and
 * ensures that changes to an entry are not applied in a different order in other members. This can
 * cause operations to be conflated, so that some cache listeners may see an event while others do
 * not, but it guarantees that the system will be consistent.</dd>
 *
 * <dt>StatisticsEnabled [<em>default:</em> {@code false}]</dt>
 * <dd>Whether statistics are enabled for this region. The default is disabled, which conserves on
 * memory. <br>
 * {@link #setStatisticsEnabled} {@link RegionAttributes#getStatisticsEnabled}</dd>
 *
 * <dt>IgnoreJTA [<em>default:</em> {@code false}]</dt>
 * <dd>Whether JTA transactions are ignored for this region. The default is to look for and join JTA
 * transactions for operations performed on a region.
 *
 * <dt>DiskStoreName [<em>default:</em> {@code null}, meaning no disk store]</dt>
 * <dd>If not {@code null} then this region will write its data to the named {@link DiskStore}.<br>
 * {@link #setDiskStoreName} {@link RegionAttributes#getDiskStoreName}</dd>
 *
 * <dt>DiskSynchronous [<em>default:</em> {@code true}]</dt>
 * <dd>If {@code true} then any writes to disk done for this region will be done synchronously. This
 * means that they will be in the file system buffer before the operation doing the write
 * returns.<br>
 * If {@code false} then any writes to disk done for this region will be done asynchronously. This
 * means that they are queued up to be written and when they are actually written to the file system
 * buffer is determined by the region's {@link DiskStore} configuration. Asynchronous writes will be
 * conflated if the same entry is written while a previous operation for the same entry is still in
 * the queue.<br>
 * {@link #setDiskSynchronous} {@link RegionAttributes#isDiskSynchronous}</dd>
 *
 * <dt>PersistBackup [<em>default:</em> {@code false}]</dt>
 * <dd>Whether or not a persistent backup should be made of the region.<br>
 * {@link #setPersistBackup} {@link RegionAttributes#getPersistBackup}</dd>
 * <dd><em>Deprecated</em>, use {@link DataPolicy#PERSISTENT_REPLICATE} or
 * {@link DataPolicy#PERSISTENT_PARTITION} instead.</dd>
 *
 * <dt>DiskWriteAttributes [<em>default:</em> Asynchronously write to disk every second (a
 * {@code timeInterval} of 1000 and a {@code byteThreshold} of 0). {@code rollOplogs} is set to true
 * and {@code maxOplogSize} is set to 1024 MB]</dt>
 * <dd>How region data should be written to disk. Determines whether data should be written
 * synchronously or asynchronously. Data that is written asynchronously can be written at a certain
 * {@linkplain DiskWriteAttributes#getTimeInterval time interval} or once a certain number of
 * {@linkplain DiskWriteAttributes#getBytesThreshold bytes of data} have been enqueued.<br>
 * {@link DiskWriteAttributes} {@link #setDiskWriteAttributes}
 * {@link RegionAttributes#getDiskWriteAttributes}</dd>
 * <dd><em>Deprecated</em>, use {@link #setDiskStoreName} and {@link #setDiskSynchronous}
 * instead.</dd>
 *
 * <dt>DiskDirs [<em>default:</em> Current working directory ({@code user.dir}
 * {@linkplain System#getProperties system property})]</dt>
 * <dd>The directories to which the region's data are written. If multiple directories are used,
 * GemFire will attempt to distribute the data evenly among them. <br>
 * {@link #setDiskDirs} {@link RegionAttributes#getDiskDirs}</dd>
 * <dd><em>Deprecated</em>, use {@link #setDiskStoreName} instead.</dd>
 *
 * <dt>DiskDirSizes [<em>default:</em> 10240 MB]</dt>
 * <dd>The size of the directory to which region's data is written.<br>
 * {@link #setDiskDirsAndSizes} {@link RegionAttributes#getDiskDirSizes}</dd>
 * <dd><em>Deprecated</em>, use {@link #setDiskStoreName} instead.</dd>
 *
 *
 * <dt>{@link PartitionAttributes} [<em>default:</em> {@code null}, meaning no region
 * partitioning]</dt>
 * <dd>How region data is partitioned among the members of the distributed system. <br>
 * {@link #setPartitionAttributes} {@link RegionAttributes#getPartitionAttributes}</dd>
 *
 * <dt>{@link MembershipAttributes} [<em>default:</em> no required roles]</dt>
 * <dd>How access to the region is affected when one or more required roles are missing from the
 * region membership. <br>
 * {@link #setMembershipAttributes} {@link RegionAttributes#getMembershipAttributes}</dd>
 * <dd><em>Deprecated</em>, this API is scheduled to be removed</dd>
 * </dl>
 *
 * <h4>Locking</h4>
 * <dl>
 * <dt>LockGrantor [<em>default:</em> {@code false}]</dt>
 * <dd>Should this process become lock grantor for the region?</dd>
 * {@link #setLockGrantor} {@link RegionAttributes#isLockGrantor} {@link Region#becomeLockGrantor}
 * </dl>
 *
 * <h4>Querying</h4>
 * <dl>
 * <dt>IndexMaintenanceSynchronous [<em>default:</em> {@code false}]</dt>
 * <dd>Are indexes built over in this region updated synchronously when the underlying data is
 * modified?</dd>
 * {@link #setIndexMaintenanceSynchronous} {@link RegionAttributes#getIndexMaintenanceSynchronous}
 * </dl>
 *
 * <p>
 * Note that the RegionAttributes are not distributed with the region.
 *
 * <a name="compatibility"></a>
 * <h3>Compatibility Rules</h3>
 * <h4>RegionAttributes Creation Constraints</h4> If any of the following compatibility rules are
 * violated when {@link #create}} is called then an {@link IllegalStateException} is thrown. See
 * {@link #validateAttributes}.
 *
 * <a name="creationConstraints"></a>
 * <h3>Creation Constraints</h3>
 * <h4>Region Creation Constraints on RegionAttributes</h4>
 *
 * If any of the following rules are violated when {@link Region#createSubregion createSubregion} or
 * {@link Cache#createRegion createRegion} are called, then an {@code IllegalStateException} is
 * thrown.
 *
 * <ul>
 * <li>A region with {@code Scope.LOCAL} can only have subregions with {@code Scope.LOCAL}.</li>
 * <li>{@code Scope.GLOBAL} is illegal if there is any other cache in the distributed system that
 * has the same region with {@code Scope.DISTRIBUTED_NO_ACK} or {@code Scope.DISTRIBUTED_ACK}.</li>
 * <li>{@code Scope.DISTRIBUTED_ACK} is illegal if there is any other cache in the distributed
 * system that has the same region with {@code Scope.DISTRIBUTED_NO_ACK} or
 * {@code Scope.GLOBAL}.</li>
 * <li>{@code Scope.DISTRIBUTED_NO_ACK} is illegal if there is any other cache in the distributed
 * system that has the same region with {@code Scope.DISTRIBUTED_ACK} or {@code Scope.GLOBAL}.</li>
 * </ul>
 *
 * @see RegionAttributes
 * @see AttributesMutator
 * @see Region#createSubregion(String, RegionAttributes)
 *
 * @since GemFire 3.0
 * @deprecated as of 6.5 use {@link Cache#createRegionFactory(RegionShortcut)} or
 *             {@link ClientCache#createClientRegionFactory(ClientRegionShortcut)} instead.
 */
@Deprecated
@SuppressWarnings("synthetic-access")
public class AttributesFactory<K, V> {
  private final RegionAttributesImpl<K, V> regionAttributes = new RegionAttributesImpl<>();

  /**
   * The default disk synchronous write setting
   * <p>
   * Current value: {@code true} each.
   *
   * @since GemFire 6.5
   */
  public static final boolean DEFAULT_DISK_SYNCHRONOUS = true;

  /**
   * The default concurrency level for updates to region values
   */
  public static final int DEFAULT_CONCURRENCY_LEVEL = 16;

  /**
   * Creates a new instance of AttributesFactory ready to create a {@code RegionAttributes} with
   * default settings.
   */
  public AttributesFactory() {}

  /**
   * Creates a new instance of AttributesFactory ready to create a {@code RegionAttributes} with the
   * same settings as those in the specified {@code RegionAttributes}.
   *
   * @param regionAttributes the {@code RegionAttributes} used to initialize this AttributesFactory
   */
  public AttributesFactory(RegionAttributes<K, V> regionAttributes) {
    synchronized (this.regionAttributes) {
      this.regionAttributes.cacheListeners =
          new ArrayList<>(Arrays.asList(regionAttributes.getCacheListeners()));
    }
    this.regionAttributes.cacheLoader = regionAttributes.getCacheLoader();
    this.regionAttributes.cacheWriter = regionAttributes.getCacheWriter();
    this.regionAttributes.regionTimeToLive = regionAttributes.getRegionTimeToLive().getTimeout();
    this.regionAttributes.regionTimeToLiveExpirationAction =
        regionAttributes.getRegionTimeToLive().getAction();
    this.regionAttributes.regionIdleTimeout = regionAttributes.getRegionIdleTimeout().getTimeout();
    this.regionAttributes.regionIdleTimeoutExpirationAction =
        regionAttributes.getRegionIdleTimeout().getAction();

    this.regionAttributes.entryTimeToLive = regionAttributes.getEntryTimeToLive().getTimeout();
    this.regionAttributes.entryTimeToLiveExpirationAction =
        regionAttributes.getEntryTimeToLive().getAction();
    this.regionAttributes.customEntryTimeToLive = regionAttributes.getCustomEntryTimeToLive();
    this.regionAttributes.entryIdleTimeout = regionAttributes.getEntryIdleTimeout().getTimeout();
    this.regionAttributes.entryIdleTimeoutExpirationAction =
        regionAttributes.getEntryIdleTimeout().getAction();
    this.regionAttributes.customEntryIdleTimeout = regionAttributes.getCustomEntryIdleTimeout();

    this.regionAttributes.scope = regionAttributes.getScope();
    this.regionAttributes.dataPolicy = regionAttributes.getDataPolicy();
    this.regionAttributes.statisticsEnabled = regionAttributes.getStatisticsEnabled();
    this.regionAttributes.ignoreJTA = regionAttributes.getIgnoreJTA();
    this.regionAttributes.keyConstraint = regionAttributes.getKeyConstraint();
    this.regionAttributes.valueConstraint = regionAttributes.getValueConstraint();
    this.regionAttributes.initialCapacity = regionAttributes.getInitialCapacity();
    this.regionAttributes.loadFactor = regionAttributes.getLoadFactor();
    this.regionAttributes.concurrencyLevel = regionAttributes.getConcurrencyLevel();
    this.regionAttributes.concurrencyChecksEnabled = regionAttributes.getConcurrencyChecksEnabled();
    this.regionAttributes.earlyAck = regionAttributes.getEarlyAck();
    this.regionAttributes.diskStoreName = regionAttributes.getDiskStoreName();
    if (this.regionAttributes.diskStoreName == null) {
      this.regionAttributes.diskWriteAttributes = regionAttributes.getDiskWriteAttributes();
      this.regionAttributes.diskDirs = regionAttributes.getDiskDirs();
      this.regionAttributes.diskSizes = regionAttributes.getDiskDirSizes();
    }
    this.regionAttributes.diskSynchronous = regionAttributes.isDiskSynchronous();
    this.regionAttributes.indexMaintenanceSynchronous =
        regionAttributes.getIndexMaintenanceSynchronous();
    this.regionAttributes.partitionAttributes = regionAttributes.getPartitionAttributes();
    this.regionAttributes.evictionAttributes =
        (EvictionAttributesImpl) regionAttributes.getEvictionAttributes();

    this.regionAttributes.membershipAttributes = regionAttributes.getMembershipAttributes();
    this.regionAttributes.subscriptionAttributes = regionAttributes.getSubscriptionAttributes();
    this.regionAttributes.evictionAttributes =
        (EvictionAttributesImpl) regionAttributes.getEvictionAttributes();

    this.regionAttributes.publisher = regionAttributes.getPublisher();
    this.regionAttributes.enableAsyncConflation = regionAttributes.getEnableAsyncConflation();
    this.regionAttributes.enableSubscriptionConflation =
        regionAttributes.getEnableSubscriptionConflation();
    this.regionAttributes.poolName = regionAttributes.getPoolName();
    this.regionAttributes.isCloningEnabled = regionAttributes.getCloningEnabled();
    this.regionAttributes.multicastEnabled = regionAttributes.getMulticastEnabled();
    this.regionAttributes.gatewaySenderIds =
        new CopyOnWriteArraySet<>(regionAttributes.getGatewaySenderIds());
    this.regionAttributes.asyncEventQueueIds =
        new CopyOnWriteArraySet<>(regionAttributes.getAsyncEventQueueIds());
    this.regionAttributes.isLockGrantor = regionAttributes.isLockGrantor(); // fix for bug 47067
    if (regionAttributes instanceof UserSpecifiedRegionAttributes) {
      this.regionAttributes
          .setIndexes(((UserSpecifiedRegionAttributes<K, V>) regionAttributes).getIndexes());
    }

    if (regionAttributes instanceof UserSpecifiedRegionAttributes) {
      // Selectively set has* fields to true, propigating those non-default
      // (aka user specified) fields as such
      UserSpecifiedRegionAttributes<K, V> nonDefault =
          (UserSpecifiedRegionAttributes<K, V>) regionAttributes;
      this.regionAttributes.initHasFields(nonDefault);
      this.regionAttributes.requiresPoolName = nonDefault.requiresPoolName;
    } else {
      // Set all fields to false, essentially starting with a new set of defaults
      this.regionAttributes.setAllHasFields(false);
    }

    this.regionAttributes.compressor = regionAttributes.getCompressor();
    this.regionAttributes.offHeap = regionAttributes.getOffHeap();
  }

  // CALLBACKS

  /**
   * Sets the cache loader for the next {@code RegionAttributes} created.
   *
   * @param cacheLoader the cache loader or null if no loader
   * @throws IllegalStateException if this region has a {@link #setPoolName pool name set}
   */
  public void setCacheLoader(CacheLoader<K, V> cacheLoader) {
    regionAttributes.cacheLoader = cacheLoader;
    regionAttributes.setHasCacheLoader(true);
  }

  /**
   * Sets the cache writer for the next {@code RegionAttributes} created.
   *
   * @param cacheWriter the cache writer or null if no cache writer
   * @throws IllegalStateException if this region has a {@link #setPoolName pool name set}
   */
  public void setCacheWriter(CacheWriter<K, V> cacheWriter) {
    regionAttributes.cacheWriter = cacheWriter;
    regionAttributes.setHasCacheWriter(true);
  }

  /**
   * Sets the CacheListener for the next {@code RegionAttributes} created. Any existing cache
   * listeners on this factory are removed.
   *
   * @param aListener a user defined CacheListener, null if no listener
   * @deprecated as of GemFire 5.0, use {@link #addCacheListener} instead.
   */
  @Deprecated
  public void setCacheListener(CacheListener<K, V> aListener) {
    ArrayList<CacheListener<K, V>> col;
    if (aListener == null) {
      col = null;
    } else {
      col = new ArrayList<>(1);
      col.add(aListener);
    }
    synchronized (regionAttributes) {
      regionAttributes.cacheListeners = col;
    }
    regionAttributes.setHasCacheListeners(true);
  }

  /**
   * Adds a cache listener to the end of the list of cache listeners on this factory.
   *
   * @param aListener the cache listener to add to the factory.
   * @throws IllegalArgumentException if {@code aListener} is null
   * @since GemFire 5.0
   */
  public void addCacheListener(CacheListener<K, V> aListener) {
    if (aListener == null) {
      throw new IllegalArgumentException(
          "addCacheListener parameter was null");
    }
    synchronized (regionAttributes) {
      regionAttributes.addCacheListener(aListener);
    }
  }

  /**
   * Removes all cache listeners and then adds each listener in the specified array.
   *
   * @param newListeners a possibly null or empty array of listeners to add to this factory.
   * @throws IllegalArgumentException if the {@code newListeners} array has a null element
   * @since GemFire 5.0
   */
  public void initCacheListeners(CacheListener<K, V>[] newListeners) {
    synchronized (regionAttributes) {
      if (newListeners == null || newListeners.length == 0) {
        regionAttributes.cacheListeners = null;
      } else {
        List<CacheListener<K, V>> nl = Arrays.asList(newListeners);
        if (nl.contains(null)) {
          throw new IllegalArgumentException(
              "initCacheListeners parameter had a null element");
        }
        regionAttributes.cacheListeners = new ArrayList<>(nl);
      }
    }
    regionAttributes.setHasCacheListeners(true);
  }


  // EXPIRATION ATTRIBUTES

  /**
   * Sets the idleTimeout expiration attributes for region entries for the next
   * {@code RegionAttributes} created. Default is 0 which indicates no expiration of this type.
   *
   * @param idleTimeout the idleTimeout ExpirationAttributes for entries in this region
   * @throws IllegalArgumentException if idleTimeout is null
   */
  public void setEntryIdleTimeout(ExpirationAttributes idleTimeout) {
    if (idleTimeout == null) {
      throw new IllegalArgumentException(
          "idleTimeout must not be null");
    }
    regionAttributes.entryIdleTimeout = idleTimeout.getTimeout();
    regionAttributes.entryIdleTimeoutExpirationAction = idleTimeout.getAction();
    regionAttributes.setHasEntryIdleTimeout(true);
  }

  /**
   * Sets the idleTimeout CustomExpiry for the next {@code RegionAttributes} created.
   *
   * @param custom the CustomExpiry to use; null means none will be used.
   */
  public void setCustomEntryIdleTimeout(CustomExpiry<K, V> custom) {
    regionAttributes.customEntryIdleTimeout = custom;
    regionAttributes.setHasCustomEntryIdleTimeout(true);
  }

  /**
   * Sets the timeToLive expiration attributes for region entries for the next
   * {@code RegionAttributes} created. Default is 0 which indicates no expiration of this type.
   *
   * @param timeToLive the timeToLive ExpirationAttributes for entries in this region
   * @throws IllegalArgumentException if timeToLive is null
   */
  public void setEntryTimeToLive(ExpirationAttributes timeToLive) {
    if (timeToLive == null) {
      throw new IllegalArgumentException(
          "timeToLive must not be null");
    }
    regionAttributes.entryTimeToLive = timeToLive.getTimeout();
    regionAttributes.entryTimeToLiveExpirationAction = timeToLive.getAction();
    regionAttributes.setHasEntryTimeToLive(true);
  }

  /**
   * Sets the custom timeToLive for the next {@code RegionAttributes} created.
   *
   * @param custom the CustomExpiry to use, none if the default for the region is to be used.
   */
  public void setCustomEntryTimeToLive(CustomExpiry<K, V> custom) {
    regionAttributes.customEntryTimeToLive = custom;
    regionAttributes.setHasCustomEntryTimeToLive(true);
  }

  /**
   * Sets the idleTimeout expiration attributes for the region itself for the next
   * {@code RegionAttributes} created. Default is 0 which indicates no expiration of this type is
   * set.
   *
   * @param idleTimeout the ExpirationAttributes for this region idleTimeout
   * @throws IllegalArgumentException if idleTimeout is null
   */
  public void setRegionIdleTimeout(ExpirationAttributes idleTimeout) {
    if (idleTimeout == null) {
      throw new IllegalArgumentException(
          "idleTimeout must not be null");
    }
    regionAttributes.regionIdleTimeout = idleTimeout.getTimeout();
    regionAttributes.regionIdleTimeoutExpirationAction = idleTimeout.getAction();
    regionAttributes.setHasRegionIdleTimeout(true);
  }


  /**
   * Sets the timeToLive expiration attributes for the region itself for the next
   * {@code RegionAttributes} created. Default is 0 i.e. no expiration of this type.
   *
   * @param timeToLive the ExpirationAttributes for this region timeToLive
   * @throws IllegalArgumentException if timeToLive is null
   */
  public void setRegionTimeToLive(ExpirationAttributes timeToLive) {
    if (timeToLive == null) {
      throw new IllegalArgumentException(
          "timeToLive must not be null");
    }
    regionAttributes.regionTimeToLive = timeToLive.getTimeout();
    regionAttributes.regionTimeToLiveExpirationAction = timeToLive.getAction();
    regionAttributes.setHasRegionTimeToLive(true);
  }

  // DISTRIBUTION ATTRIBUTES

  /**
   * Sets the scope for the next {@code RegionAttributes} created. Default scope is
   * DISTRIBUTED_NO_ACK. Refer gemfire documentation for more details on this.
   *
   * @param scopeType the type of Scope to use for the region
   * @throws IllegalArgumentException if scopeType is null
   */
  public void setScope(Scope scopeType) {
    if (scopeType == null) {
      throw new IllegalArgumentException(
          "scopeType must not be null");
    }
    regionAttributes.setScope(scopeType);
  }

  // STORAGE ATTRIBUTES

  /**
   * Sets the EvictionController for the next {@code RegionAttributes} created. Use one of the
   * creation methods on {@link EvictionAttributes} e.g.
   * {@link EvictionAttributes#createLRUHeapAttributes()} to create the desired instance for this
   * {@code AttributesFactory}
   *
   * @param evictAttrs Explains how and when eviction occurs in the Region.
   */
  public void setEvictionAttributes(EvictionAttributes evictAttrs) {
    if (evictAttrs != null) {
      regionAttributes.evictionAttributes = (EvictionAttributesImpl) evictAttrs;
    } else {
      regionAttributes.evictionAttributes = new EvictionAttributesImpl();
    }
    regionAttributes.setHasEvictionAttributes(true);
  }

  /**
   * Sets the mirror type for the next {@code RegionAttributes} created.
   *
   * @param mirrorType The type of mirroring to use for the region
   * @throws IllegalArgumentException if mirrorType is null
   * @deprecated use {@link #setDataPolicy} instead.
   */
  @Deprecated
  public void setMirrorType(MirrorType mirrorType) {
    if (mirrorType == null) {
      throw new IllegalArgumentException(
          "mirrorType must not be null");
    }
    DataPolicy dp = mirrorType.getDataPolicy();
    if (dp.withReplication()) {
      // requested a mirror type that has replication
      // if current data policy is not replicated change it
      if (!regionAttributes.getDataPolicy().withReplication()) {
        setDataPolicy(dp);
      }
    } else {
      // requested a mirror type none;
      // if current data policy is replicated change it
      if (regionAttributes.getDataPolicy().withReplication()) {
        setDataPolicy(dp);
      }
    }
  }

  /**
   * Sets the data policy for the next {@code RegionAttributes} created. Default data policy is
   * 'Normal'. Please refer gemfire documentation for more details on this.
   *
   * @param dataPolicy The data policy to use for the region
   * @throws IllegalArgumentException if dataPolicy is null
   */
  public void setDataPolicy(DataPolicy dataPolicy) {
    if (dataPolicy == null) {
      throw new IllegalArgumentException(
          "dataPolicy must not be null");
    }
    if (regionAttributes.partitionAttributes != null) {
      if (!PartitionedRegionHelper.ALLOWED_DATA_POLICIES.contains(dataPolicy)) {
        throw new IllegalStateException(
            String.format("Data policies other than %s are not supported for Partitioned Regions",
                PartitionedRegionHelper.ALLOWED_DATA_POLICIES));
      }
    }
    regionAttributes.setDataPolicy(dataPolicy);
  }



  /**
   * Sets the key constraint for the next {@code RegionAttributes} created. Keys in the region will
   * be constrained to this class (or subclass). Any attempt to store a key of an incompatible type
   * in the region will cause a {@code ClassCastException} to be thrown.
   *
   * @param keyConstraint The Class to constrain the keys to, or null if no constraint
   * @throws IllegalArgumentException if {@code keyConstraint} is a class denoting a primitive type
   */
  public void setKeyConstraint(Class<K> keyConstraint) {
    if (keyConstraint != null && keyConstraint.isPrimitive()) {
      throw new IllegalArgumentException(
          "keyConstraint must not be a primitive type");
    }
    regionAttributes.keyConstraint = keyConstraint;
    regionAttributes.setHasKeyConstraint(true);
  }

  /**
   * Sets the value constraint for the next {@code RegionAttributes} created. Values in the region
   * will be constrained to this class (or subclass). Any attempt to store a value of an
   * incompatible type in the region will cause a {@code ClassCastException} to be thrown.
   *
   * @param valueConstraint The Class to constrain the values to, or null if no constraint
   * @throws IllegalArgumentException if {@code valueConstraint} is a class denoting a primitive
   *         type
   */
  public void setValueConstraint(Class<V> valueConstraint) {
    if (valueConstraint != null && valueConstraint.isPrimitive()) {
      throw new IllegalArgumentException(
          "valueConstraint must not be a primitive type");
    }
    regionAttributes.valueConstraint = valueConstraint;
    regionAttributes.setHasValueConstraint(true);
  }



  // MAP ATTRIBUTES
  /**
   * Sets the entry initial capacity for the next {@code RegionAttributes} created. This value is
   * used in initializing the map that holds the entries. Default is 16.
   *
   * @param initialCapacity the initial capacity of the entry map
   * @throws IllegalArgumentException if initialCapacity is negative.
   * @see java.util.HashMap
   */
  public void setInitialCapacity(int initialCapacity) {
    if (initialCapacity < 0) {
      throw new IllegalArgumentException(
          "initialCapacity must be >= 0");
    }
    regionAttributes.initialCapacity = initialCapacity;
    regionAttributes.setHasInitialCapacity(true);
  }

  /**
   * Sets the entry load factor for the next {@code RegionAttributes} created. This value is used in
   * initializing the map that holds the entries. Default is 0.75.
   *
   * @param loadFactor the load factor of the entry map
   * @throws IllegalArgumentException if loadFactor is nonpositive
   * @see java.util.HashMap
   */
  public void setLoadFactor(float loadFactor) {
    if (loadFactor <= 0) {
      throw new IllegalArgumentException(
          String.format("loadFactor must be > 0, value is %s",
              loadFactor));
    }
    regionAttributes.loadFactor = loadFactor;
    regionAttributes.setHasLoadFactor(true);
  }

  /**
   * Sets the concurrency level of the next {@code RegionAttributes} created. This value is used in
   * initializing the map that holds the entries. Default is 16.
   *
   * @param concurrencyLevel the concurrency level of the entry map
   * @throws IllegalArgumentException if concurrencyLevel is nonpositive
   */
  public void setConcurrencyLevel(int concurrencyLevel) {
    if (concurrencyLevel <= 0) {
      throw new IllegalArgumentException(
          "concurrencyLevel must be > 0");
    }
    regionAttributes.concurrencyLevel = concurrencyLevel;
    regionAttributes.setHasConcurrencyLevel(true);
  }

  /**
   * Enables or disabled concurrent modification checks. Concurrency checks are enabled by default.
   *
   * @since GemFire 7.0
   * @param concurrencyChecksEnabled whether to perform concurrency checks on operations
   */
  public void setConcurrencyChecksEnabled(boolean concurrencyChecksEnabled) {
    regionAttributes.concurrencyChecksEnabled = concurrencyChecksEnabled;
    regionAttributes.setHasConcurrencyChecksEnabled(true);
  }

  /**
   * Sets whether or not a persistent backup should be made of the region.
   *
   * @param persistBackup boolean specifying if a persistent backup should be made of the region
   *
   * @since GemFire 3.2
   * @deprecated as of GemFire 5.0, use {@link DataPolicy#PERSISTENT_REPLICATE} instead
   */
  @Deprecated
  public void setPersistBackup(boolean persistBackup) {
    if (persistBackup) {
      if (!regionAttributes.getDataPolicy().withPersistence()) {
        if (regionAttributes.getDataPolicy().withPartitioning()) {
          setDataPolicy(DataPolicy.PERSISTENT_PARTITION);
        } else {
          setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
        }
      }
    } else {
      // It is less clear what we should do here for backwards compat.
      // If the current data policy is persist then we need to change it
      // otherwise just leave it alone
      if (regionAttributes.getDataPolicy().withReplication()) {
        setDataPolicy(DataPolicy.REPLICATE);
      } else if (regionAttributes.getDataPolicy().withPartitioning()) {
        setDataPolicy(DataPolicy.PARTITION);
      }
    }
  }

  /**
   * Sets whether or not acks are sent after an operation is processed.
   *
   * @param earlyAck boolean specifying if acks are sent after an operation is processed
   *
   * @since GemFire 4.1
   * @deprecated This setting no longer has any effect.
   */
  @Deprecated
  public void setEarlyAck(boolean earlyAck) {
    regionAttributes.earlyAck = earlyAck;
    regionAttributes.setHasEarlyAck(true);
  }

  /**
   * Sets whether or not this region should be considered a publisher.
   *
   * @param v boolean specifying if this region should be considered a publisher
   *
   * @since GemFire 4.2.3
   * @deprecated as of 6.5
   */
  @Deprecated
  public void setPublisher(boolean v) {}

  /**
   * Sets whether or not conflation is enabled for sending messages to async peers. Default value is
   * false.
   *
   * @param enableAsyncConflation boolean specifying if conflation is enabled for sending messages
   *        to async peers
   *
   * @since GemFire 4.2.3
   */
  public void setEnableAsyncConflation(boolean enableAsyncConflation) {
    regionAttributes.enableAsyncConflation = enableAsyncConflation;
    regionAttributes.setHasEnableAsyncConflation(true);
  }


  /**
   * Sets whether or not conflation is enabled for sending messages from a cache server to its
   * clients. Default is false.
   *
   * @param enableSubscriptionConflation boolean specifying if conflation is enabled for sending
   *        messages from a cache server to its clients
   *
   * @since GemFire 5.0
   */
  public void setEnableSubscriptionConflation(boolean enableSubscriptionConflation) {
    regionAttributes.enableSubscriptionConflation = enableSubscriptionConflation;
    regionAttributes.setHasEnableSubscriptionConflation(true);
  }

  /**
   * adds a gateway sender to the end of list of gateway senders on this factory
   *
   * @param gatewaySenderId the ID of the gateway sender to be added
   *
   * @throws IllegalArgumentException if {@code gatewaySender} is null
   * @since GemFire 7.0
   */
  public void addGatewaySenderId(String gatewaySenderId) {
    if (gatewaySenderId == null) {
      throw new IllegalArgumentException(
          "GatewaySender id is null.");
    }
    synchronized (regionAttributes) {
      regionAttributes.addGatewaySenderId(gatewaySenderId);
    }
  }

  /**
   * Adds a AsyncEventQueue to the end of list of async event queues on this factory
   *
   * @param asyncEventQueueId the ID of the async event queue to be added
   *
   * @throws IllegalArgumentException if {@code gatewaySender} is null
   * @since GemFire 7.0
   */
  public void addAsyncEventQueueId(String asyncEventQueueId) {
    if (asyncEventQueueId == null) {
      throw new IllegalArgumentException(
          "GatewaySender id is null.");
    }
    synchronized (regionAttributes) {
      regionAttributes.addAsyncEventQueueId(asyncEventQueueId);
    }
  }

  /**
   * Sets whether or not conflation is enabled for sending messages from a cache server to its
   * clients.
   *
   * @param enableBridgeConflation boolean specifying if conflation is enabled for sending messages
   *        from a cache server to its clients
   *
   * @since GemFire 5.0
   * @deprecated as of 5.7 use {@link #setEnableSubscriptionConflation} instead.
   */
  @Deprecated
  public void setEnableBridgeConflation(boolean enableBridgeConflation) {
    setEnableSubscriptionConflation(enableBridgeConflation);
  }

  /**
   * Sets whether or not conflation is enabled for sending messages from a cache server to its
   * clients.
   *
   * @param enableBridgeConflation boolean specifying if conflation is enabled for sending messages
   *        from a cache server to its clients
   *
   * @deprecated as of GemFire 5.0, use {@link #setEnableSubscriptionConflation}
   */
  @Deprecated
  public void setEnableConflation(boolean enableBridgeConflation) {
    setEnableSubscriptionConflation(enableBridgeConflation);
  }

  /**
   * Sets the {@link DiskWriteAttributes} for this factory
   *
   * @param attrs the {@link DiskWriteAttributes} to be set for this factory
   *
   * @see Region#writeToDisk
   *
   * @since GemFire 3.2
   * @deprecated as of 6.5 use {@link #setDiskStoreName} instead
   */
  @Deprecated
  public void setDiskWriteAttributes(DiskWriteAttributes attrs) {
    if (regionAttributes.getDiskStoreName() != null) {
      throw new IllegalStateException(
          String.format("Deprecated API %s cannot be used with DiskStore %s",
              "setDiskWriteAttributes", regionAttributes.getDiskStoreName()));
    }
    regionAttributes.diskWriteAttributes = attrs;
    regionAttributes.setHasDiskWriteAttributes(true);
    if (attrs != null) {
      // keep new apis in sync with old
      regionAttributes.diskSynchronous = attrs.isSynchronous();
    }
  }

  /**
   * Sets the directories with the default size of 10240 MB to which the region's data is written
   *
   * @param diskDirs the directories to which the region's data is written
   *
   * @throws GemFireIOException if a directory does not exist
   *
   * @since GemFire 3.2
   * @deprecated as of 6.5 use {@link DiskStoreFactory#setDiskDirs} instead
   */
  @Deprecated
  public void setDiskDirs(File[] diskDirs) {
    if (regionAttributes.getDiskStoreName() != null) {
      throw new IllegalStateException(
          String.format("Deprecated API %s cannot be used with DiskStore %s",
              "setDiskDirs", regionAttributes.getDiskStoreName()));
    }
    DiskStoreFactoryImpl.checkIfDirectoriesExist(diskDirs);
    regionAttributes.diskDirs = diskDirs;
    regionAttributes.diskSizes = new int[diskDirs.length];
    for (int i = 0; i < diskDirs.length; i++) {
      regionAttributes.diskSizes[i] = DiskStoreFactory.DEFAULT_DISK_DIR_SIZE;
    }
    if (!regionAttributes.hasDiskWriteAttributes()
        && !regionAttributes.hasDiskSynchronous()) {
      // switch to the old default
      regionAttributes.diskSynchronous = false;
      regionAttributes.diskWriteAttributes = DiskWriteAttributesImpl.getDefaultAsyncInstance();
    }
    regionAttributes.setHasDiskDirs(true);
  }

  /**
   * Sets the DiskStore name attribute. This causes the region to use the {@link DiskStore}.
   *
   * @param name the name of the diskstore
   * @since GemFire 6.5
   */
  public void setDiskStoreName(String name) {
    if (regionAttributes.hasDiskDirs() || regionAttributes.hasDiskWriteAttributes()) {
      throw new IllegalStateException(
          String.format("Deprecated API %s cannot be used with DiskStore %s",
              "setDiskDirs or setDiskWriteAttributes", name));
    }
    regionAttributes.diskStoreName = name;
    regionAttributes.setHasDiskStoreName(true);
  }

  /**
   * Sets whether or not the writing to the disk is synchronous. Default is true.
   *
   * @param isSynchronous boolean if true indicates synchronous writes
   * @since GemFire 6.5
   */
  public void setDiskSynchronous(boolean isSynchronous) {
    regionAttributes.diskSynchronous = isSynchronous;
    regionAttributes.setHasDiskSynchronous(true);
    if (regionAttributes.hasDiskWriteAttributes()) {
      DiskWriteAttributesFactory dwaf =
          new DiskWriteAttributesFactory(regionAttributes.diskWriteAttributes);
      dwaf.setSynchronous(isSynchronous);
      regionAttributes.diskWriteAttributes = dwaf.create();
    } else {
      if (isSynchronous) {
        regionAttributes.diskWriteAttributes =
            DiskWriteAttributesImpl.getDefaultSyncInstance();
      } else {
        regionAttributes.diskWriteAttributes =
            DiskWriteAttributesImpl.getDefaultAsyncInstance();
      }
    }
  }

  /**
   * Sets the directories to which the region's data is written and also set their sizes in
   * megabytes
   *
   * @param diskDirs the directories to which the region's data is written
   * @param diskSizes the size in megabytes for each directory
   * @throws IllegalArgumentException if a dir does not exist or the length of the size array does
   *         not match to the length of the dir array or the given size is not a valid positive
   *         number
   *
   * @since GemFire 5.1
   * @deprecated as of 6.5 use {@link DiskStoreFactory#setDiskDirsAndSizes} instead
   */
  @Deprecated
  public void setDiskDirsAndSizes(File[] diskDirs, int[] diskSizes) {
    if (regionAttributes.getDiskStoreName() != null) {
      throw new IllegalStateException(
          String.format("Deprecated API %s cannot be used with DiskStore %s",
              "setDiskDirsAndSizes", regionAttributes.getDiskStoreName()));
    }
    DiskStoreFactoryImpl.checkIfDirectoriesExist(diskDirs);
    regionAttributes.diskDirs = diskDirs;
    if (diskSizes.length != regionAttributes.diskDirs.length) {
      throw new IllegalArgumentException(
          String.format(
              "Number of diskSizes is %s which is not equal to number of disk Dirs which is %s",
              diskSizes.length,
              diskDirs.length));
    }
    verifyNonNegativeDirSize(diskSizes);
    regionAttributes.diskSizes = diskSizes;
    if (!regionAttributes.hasDiskWriteAttributes()
        && !regionAttributes.hasDiskSynchronous()) {
      // switch to the old default
      regionAttributes.diskSynchronous = false;
      regionAttributes.diskWriteAttributes = DiskWriteAttributesImpl.getDefaultAsyncInstance();
    }
    regionAttributes.setHasDiskDirs(true);
  }

  /**
   * Sets the {@code PartitionAttributes} that describe how the region is partitioned among members
   * of the distributed system. This also establishes a data policy of {@link DataPolicy#PARTITION
   * PARTITION}, if the data policy has not already been set.
   *
   * @param partition the {@code PartitionAttributes} that describe how the region is partitioned
   *        among members of the distributed system
   *
   * @since GemFire 5.0
   */
  public void setPartitionAttributes(PartitionAttributes partition) {
    if (partition != null) {
      if (!regionAttributes.hasDataPolicy()) {
        regionAttributes.dataPolicy = PartitionedRegionHelper.DEFAULT_DATA_POLICY;
      } else if (!PartitionedRegionHelper.ALLOWED_DATA_POLICIES
          .contains(regionAttributes.dataPolicy)) {
        throw new IllegalStateException(
            String.format(
                "Data policy %s is not allowed for a partitioned region. DataPolicies other than %s are not allowed.",
                regionAttributes.dataPolicy,
                PartitionedRegionHelper.ALLOWED_DATA_POLICIES));
      }
      if (regionAttributes.hasPartitionAttributes()
          && regionAttributes.partitionAttributes instanceof PartitionAttributesImpl
          && partition instanceof PartitionAttributesImpl) {
        // Make a copy and call merge on it to prevent bug 51616
        PartitionAttributesImpl copy =
            ((PartitionAttributesImpl) regionAttributes.partitionAttributes).copy();
        copy.merge((PartitionAttributesImpl) partition);
        regionAttributes.partitionAttributes = copy;
      } else {
        regionAttributes.partitionAttributes = partition;
        regionAttributes.setHasPartitionAttributes(true);
      }

      ((PartitionAttributesImpl) regionAttributes.partitionAttributes)
          .setOffHeap(regionAttributes.offHeap);
    } else {
      regionAttributes.partitionAttributes = null;
      regionAttributes.setHasPartitionAttributes(false);
    }
  }

  protected void setBucketRegion(boolean b) {
    regionAttributes.isBucketRegion = b;
  }

  /**
   * Sets the {@code MembershipAttributes} that describe the membership roles required for reliable
   * access to the region.
   *
   * @param membership the {@code MembershipAttributes} that describe the membership roles required
   *        for reliable access to the region
   *
   * @deprecated this API is scheduled to be removed
   */
  @Deprecated
  public void setMembershipAttributes(MembershipAttributes membership) {
    regionAttributes.membershipAttributes = membership;
    regionAttributes.setHasMembershipAttributes(true);
  }

  /**
   * Sets the {@code SubscriptionAttributes} that describe how the region will subscribe to other
   * distributed cache instances of the region.
   *
   * @param subscription the {@code SubscriptionAttributes} that describe how the region will
   *        subscribe to other distributed cache instances of the region
   *
   * @since GemFire 5.0
   */
  public void setSubscriptionAttributes(SubscriptionAttributes subscription) {
    regionAttributes.subscriptionAttributes = subscription;
    regionAttributes.setHasSubscriptionAttributes(true);
  }

  /**
   * Set how indexes on the region should be maintained. It will be either synchronous or
   * asynchronous. Default is true.
   *
   * @param synchronous boolean specifying if index maintenance on the region should be synchronous
   *        (true) or asynchronous (false)
   */
  public void setIndexMaintenanceSynchronous(boolean synchronous) {
    regionAttributes.indexMaintenanceSynchronous = synchronous;
    regionAttributes.setHasIndexMaintenanceSynchronous(true);
  }

  // STATISTICS
  /**
   * Sets whether statistics are enabled for this region and its entries. Default is false.
   *
   * @param statisticsEnabled whether statistics are enabled
   */
  public void setStatisticsEnabled(boolean statisticsEnabled) {
    regionAttributes.statisticsEnabled = statisticsEnabled;
    regionAttributes.setHasStatisticsEnabled(true);
  }

  /**
   * Sets the flag telling a region to ignore JTA transactions. Default is false.
   *
   * @param flag boolean specifying if the region should ignore JTA transactions
   *
   * @since GemFire 5.0
   */
  public void setIgnoreJTA(boolean flag) {
    regionAttributes.ignoreJTA = flag;
    regionAttributes.setHasIgnoreJTA(true);
  }

  /**
   * Sets whether this region should become lock grantor. Default value is false.
   *
   * @param isLockGrantor whether this region should become lock grantor
   */
  public void setLockGrantor(boolean isLockGrantor) {
    regionAttributes.isLockGrantor = isLockGrantor;
    regionAttributes.setHasIsLockGrantor(true);
  }

  /**
   * Sets whether distributed operations on this region should attempt to use multicast. Multicast
   * must also be enabled in the cache's DistributedSystem
   * (see <a href="../distributed/DistributedSystem.html#mcast-port">"mcast-port"</a>).
   * Default is false.
   *
   * @param value boolean specifying if distributed operations on this region should attempt to use
   *        multicast
   *
   * @since GemFire 5.0
   * @see RegionAttributes#getMulticastEnabled
   */
  public void setMulticastEnabled(boolean value) {
    regionAttributes.multicastEnabled = value;
    regionAttributes.setHasMulticastEnabled(true);
  }

  /**
   * Sets cloning on region. Default is false. Note: off-heap regions always behave as if cloning is
   * enabled.
   *
   * @param cloningEnable boolean specifying if cloning is enabled on the region
   *
   * @since GemFire 6.1
   * @see RegionAttributes#getCloningEnabled()
   */
  public void setCloningEnabled(boolean cloningEnable) {
    regionAttributes.isCloningEnabled = cloningEnable;
    regionAttributes.setHasCloningEnabled(true);
  }


  /**
   * Sets the pool name attribute. This causes regions that use these attributes to be a client
   * region which communicates with the servers that the connection pool communicates with.
   * <p>
   * If this attribute is set to {@code null} or {@code ""} then the connection pool is disabled
   * causing regions that use these attributes to be communicate with peers instead of servers.
   * <p>
   * The named connection pool must exist on the cache at the time these attributes are used to
   * create a region. See {@link PoolManager#createFactory} for how to create a connection pool.
   *
   * @param name the name of the connection pool to use; if {@code null} or {@code ""} then the
   *        connection pool is disabled for regions using these attributes.
   * @since GemFire 5.7
   */
  public void setPoolName(String name) {
    String nm = name;
    if ("".equals(nm)) {
      nm = null;
    }
    regionAttributes.poolName = nm;
    regionAttributes.setHasPoolName(true);

  }

  /**
   * Sets this region's compressor for compressing entry values.
   *
   * @since GemFire 8.0
   * @param compressor a compressor.
   */
  public void setCompressor(Compressor compressor) {
    regionAttributes.compressor = compressor;
    regionAttributes.setHasCompressor(true);

    // Cloning must be enabled when a compressor is set
    if (compressor != null) {
      setCloningEnabled(true);
    }
  }

  /**
   * Enables this region's usage of off-heap memory if true.
   *
   * @since Geode 1.0
   * @param offHeap boolean flag to enable off-heap memory
   */
  public void setOffHeap(boolean offHeap) {
    regionAttributes.offHeap = offHeap;
    regionAttributes.setHasOffHeap(true);

    if (regionAttributes.partitionAttributes != null) {
      ((PartitionAttributesImpl) regionAttributes.partitionAttributes).setOffHeap(offHeap);
    }
  }

  // FACTORY METHOD

  /**
   * Creates a {@code RegionAttributes} with the current settings.
   *
   * @return the newly created {@code RegionAttributes}
   * @throws IllegalStateException if the current settings violate the
   *         <a href="#compatibility">compatibility rules</a>
   * @deprecated as of GemFire 5.0, use {@link #create} instead
   */
  @Deprecated
  public RegionAttributes<K, V> createRegionAttributes() {
    return create();
  }

  /**
   * Creates a {@code RegionAttributes} with the current settings.
   *
   * @return the newly created {@code RegionAttributes}
   * @throws IllegalStateException if the current settings violate the
   *         <a href="#compatibility">compatibility rules</a>
   * @since GemFire 5.0
   */
  @SuppressWarnings("unchecked")
  public RegionAttributes<K, V> create() {
    if (regionAttributes.hasDataPolicy() && regionAttributes.dataPolicy.withPartitioning()
        && regionAttributes.partitionAttributes == null) {
      regionAttributes.partitionAttributes = (new PartitionAttributesFactory()).create();
      // fix bug #52033 by invoking setOffHeap now (localMaxMemory may now be the temporary
      // placeholder for off-heap until DistributedSystem is created
      ((PartitionAttributesImpl) regionAttributes.partitionAttributes)
          .setOffHeap(regionAttributes.getOffHeap());
    }
    // As of 6.5 we automatically enable stats if expiration is used.
    {
      RegionAttributesImpl attrs = regionAttributes;
      if (!attrs.hasStatisticsEnabled() && !attrs.getStatisticsEnabled()
          && (attrs.getRegionTimeToLive().getTimeout() != 0
              || attrs.getRegionIdleTimeout().getTimeout() != 0
              || attrs.getEntryTimeToLive().getTimeout() != 0
              || attrs.getEntryIdleTimeout().getTimeout() != 0
              || attrs.getCustomEntryIdleTimeout() != null
              || attrs.getCustomEntryTimeToLive() != null)) {
        // @todo we could do some more implementation work so that we would
        // not need to enable stats unless entryIdleTimeout is enabled.
        // We need the stats in that case because we need a new type of RegionEntry
        // so we know that last time it was accessed. But for all the others we
        // the stat less region keeps track of everything we need.
        // The only problem is that some places in the code are conditionalized
        // on statisticsEnabled.
        setStatisticsEnabled(true);
      }
      if (attrs.getDataPolicy().withReplication() && !attrs.getDataPolicy().withPersistence()
          && attrs.getScope().isDistributed()) {
        RegionAttributesImpl<?, ?> rattr = attrs;
        if (!attrs.isForBucketRegion()) {
          if (attrs.getEvictionAttributes().getAction().isLocalDestroy()
              || attrs.getEntryIdleTimeout().getAction().isLocal()
              || attrs.getEntryTimeToLive().getAction().isLocal()
              || attrs.getRegionIdleTimeout().getAction().isLocalInvalidate()
              || attrs.getRegionTimeToLive().getAction().isLocalInvalidate()) {
            // new to 6.5; switch to PRELOADED and interest ALL
            setDataPolicy(DataPolicy.PRELOADED);
            setSubscriptionAttributes(new SubscriptionAttributes(InterestPolicy.ALL));
          }
        }
      }
    }
    validateAttributes(regionAttributes);
    return (RegionAttributes<K, V>) regionAttributes.clone();
  }

  /**
   * Validates that the attributes are consistent with each other. The following rules are checked
   * and enforced:
   * <ul>
   * <li>If the data policy {@link DataPolicy#withReplication uses replication} and the scope is
   * {@link Scope#isDistributed distributed} then the following are incompatible:
   * <ul>
   * <li>ExpirationAction.LOCAL_INVALIDATE on the region</li>
   * <li>ExpirationAction.LOCAL_DESTROY on the entries</li>
   * <li>ExpirationAction.LOCAL_INVALIDATE on the entries</li>
   * <li>An LRU with local destroy eviction action</li>
   * </ul>
   * </li>
   * <li>Region or entry expiration is incompatible with disabled statistics on the region</li>
   * <li>Entry expiration is incompatible with the {@link DataPolicy#EMPTY} data policy</li>
   * <li>{@link EvictionAttributes Eviction} is incompatible with the {@link DataPolicy#EMPTY} data
   * policy</li>
   * </ul>
   *
   * @param attrs the attributes to validate
   * @throws IllegalStateException if the attributes are not consistent with each other.
   * @since GemFire 3.5
   */
  public static void validateAttributes(RegionAttributes<?, ?> attrs) {
    // enforce the creation constraints

    if (attrs.getDataPolicy().withReplication() && attrs.getScope().isDistributed()) {
      boolean isForBucketRegion = false;
      if (attrs instanceof RegionAttributesImpl) {
        RegionAttributesImpl<?, ?> regionAttributes = (RegionAttributesImpl<?, ?>) attrs;
        if (regionAttributes.isForBucketRegion()) {
          isForBucketRegion = true;
        }
      }
      if (!isForBucketRegion) {
        ExpirationAction idleAction = attrs.getEntryIdleTimeout().getAction();
        ExpirationAction ttlAction = attrs.getEntryTimeToLive().getAction();

        if (idleAction == ExpirationAction.LOCAL_DESTROY
            || ttlAction == ExpirationAction.LOCAL_DESTROY) {
          throw new IllegalStateException(
              "ExpirationAction.LOCAL_DESTROY on the entries is incompatible with distributed replication");
        }

        if (attrs.getEvictionAttributes().getAction().isLocalDestroy()) {
          throw new IllegalStateException(
              "An Eviction Controller with local destroy eviction action is incompatible with distributed replication");
        }

        if (attrs.getRegionIdleTimeout().getAction() == ExpirationAction.LOCAL_INVALIDATE
            || attrs.getRegionTimeToLive().getAction() == ExpirationAction.LOCAL_INVALIDATE) {
          throw new IllegalStateException(
              "ExpirationAction.LOCAL_INVALIDATE on the region is incompatible with distributed replication");
        }

        if (idleAction == ExpirationAction.LOCAL_INVALIDATE
            || ttlAction == ExpirationAction.LOCAL_INVALIDATE) {
          throw new IllegalStateException(
              "ExpirationAction.LOCAL_INVALIDATE on the entries is incompatible with distributed replication");
        }
      }
    }

    if (attrs.getDiskStoreName() != null) {
      EvictionAttributes ea = attrs.getEvictionAttributes();
      if (!attrs.getDataPolicy().withPersistence()
          && (ea != null && ea.getAction() != EvictionAction.OVERFLOW_TO_DISK)) {
        throw new IllegalStateException(
            "Only regions with persistence or overflow to disk can specify DiskStore");
      }
    }

    if (!attrs.getStatisticsEnabled() && (attrs.getRegionTimeToLive().getTimeout() != 0
        || attrs.getRegionIdleTimeout().getTimeout() != 0
        || attrs.getEntryTimeToLive().getTimeout() != 0
        || attrs.getEntryIdleTimeout().getTimeout() != 0
        || attrs.getCustomEntryIdleTimeout() != null || attrs.getCustomEntryTimeToLive() != null)) {
      throw new IllegalStateException(
          "Statistics must be enabled for expiration");
    }

    if (attrs.getDataPolicy() == DataPolicy.EMPTY) {
      if (attrs.getEntryTimeToLive().getTimeout() != 0
          || attrs.getEntryIdleTimeout().getTimeout() != 0
          || attrs.getCustomEntryTimeToLive() != null
          || attrs.getCustomEntryIdleTimeout() != null) {
        throw new IllegalStateException(
            String.format("If the data policy is %s then entry expiration is not allowed.",
                attrs.getDataPolicy()));
      }
      if (!attrs.getEvictionAttributes().getAlgorithm().isNone()) {
        throw new IllegalStateException(
            String.format("If the data policy is %s then eviction is not allowed.",
                attrs.getDataPolicy()));
      }
    }
    if (attrs.getMembershipAttributes().hasRequiredRoles()) {
      if (attrs.getScope().isLocal()) {
        throw new IllegalStateException(
            "If the membership attributes has required roles then scope must not be LOCAL.");
      }
    }

    final PartitionAttributes pa = attrs.getPartitionAttributes();
    // Validations for PartitionRegion Attributes
    if (pa != null) {
      ((PartitionAttributesImpl) pa)
          .validateWhenAllAttributesAreSet(attrs instanceof RegionAttributesCreation);
      ExpirationAttributes regionIdleTimeout = attrs.getRegionIdleTimeout();
      ExpirationAttributes regionTimeToLive = attrs.getRegionTimeToLive();
      AbstractRegion.validatePRRegionExpirationAttributes(regionIdleTimeout);
      AbstractRegion.validatePRRegionExpirationAttributes(regionTimeToLive);

      ExpirationAttributes entryIdleTimeout = attrs.getEntryIdleTimeout();
      ExpirationAttributes entryTimeToLive = attrs.getEntryTimeToLive();
      if ((entryIdleTimeout.getAction().isLocalDestroy() && entryIdleTimeout.getTimeout() > 0)
          || (entryTimeToLive.getAction().isLocalDestroy() && entryTimeToLive.getTimeout() > 0)) {
        throw new IllegalStateException(
            "ExpirationAction LOCAL_DESTROY is not supported for Partitioned Region.");
      }
      if ((entryIdleTimeout.getAction().isLocalInvalidate() && entryIdleTimeout.getTimeout() > 0)
          || (entryTimeToLive.getAction().isLocalInvalidate()
              && entryTimeToLive.getTimeout() > 0)) {
        throw new IllegalStateException(
            "ExpirationAction LOCAL_INVALIDATE is not supported for Partitioned Region.");
      }

      if (attrs instanceof UserSpecifiedRegionAttributes<?, ?>) {
        UserSpecifiedRegionAttributes<?, ?> rac = (UserSpecifiedRegionAttributes<?, ?>) attrs;
        if (rac.hasScope()) {
          throw new IllegalStateException(
              "Setting Scope on a Partitioned Regions is not allowed.");
        }
      }

      if (attrs.getPoolName() != null) {
        throw new IllegalStateException("Setting pool name on a Partitioned Region is not allowed");
      }
      if (pa.getTotalMaxMemory() <= 0) {
        throw new IllegalStateException(
            "Total size of partition region must be > 0.");
      }

      if (!PartitionedRegionHelper.ALLOWED_DATA_POLICIES.contains(attrs.getDataPolicy())) {
        throw new IllegalStateException(
            String.format("Data policies other than %s are not allowed in  partitioned regions.",
                PartitionedRegionHelper.ALLOWED_DATA_POLICIES));
      }
      // fix bug #52033 by invoking getLocalMaxMemoryForValidation here
      if (((PartitionAttributesImpl) pa).getLocalMaxMemoryForValidation() < 0) {
        throw new IllegalStateException(
            "PartitionAttributes localMaxMemory must not be negative.");
      }

      if (attrs.isLockGrantor() == true) {
        throw new IllegalStateException(
            "setLockGranter(true) is not allowed in Partitioned Regions.");
      }

      // fix bug #52033 by invoking getLocalMaxMemoryForValidation here
      if (((PartitionAttributesImpl) pa).getLocalMaxMemoryForValidation() == 0
          && attrs.getDataPolicy() == DataPolicy.PERSISTENT_PARTITION) {
        throw new IllegalStateException(
            "Persistence is not allowed when local-max-memory is zero.");
      }
    }

    if (null != attrs.getCompressor() && !attrs.getCloningEnabled()) {
      throw new IllegalStateException("Cloning cannot be disabled when a compressor is set.");
    }
  }

  private static class RegionAttributesImpl<K, V> extends UserSpecifiedRegionAttributes<K, V>
      implements Cloneable {
    public Set<String> gatewaySenderIds;
    public Set<String> asyncEventQueueIds;

    ArrayList<CacheListener<K, V>> cacheListeners;
    CacheLoader<K, V> cacheLoader;
    CacheWriter<K, V> cacheWriter;
    int regionTimeToLive = 0;
    ExpirationAction regionTimeToLiveExpirationAction = ExpirationAction.INVALIDATE;
    int regionIdleTimeout = 0;
    ExpirationAction regionIdleTimeoutExpirationAction = ExpirationAction.INVALIDATE;

    int entryTimeToLive = 0;
    ExpirationAction entryTimeToLiveExpirationAction = ExpirationAction.INVALIDATE;
    CustomExpiry<K, V> customEntryTimeToLive = null;
    int entryIdleTimeout = 0;
    ExpirationAction entryIdleTimeoutExpirationAction = ExpirationAction.INVALIDATE;
    CustomExpiry<K, V> customEntryIdleTimeout = null;

    Scope scope = AbstractRegion.DEFAULT_SCOPE;
    DataPolicy dataPolicy = DataPolicy.DEFAULT;
    boolean statisticsEnabled = false;
    boolean ignoreJTA = false;
    boolean isLockGrantor = false;
    Class<K> keyConstraint = null;
    Class<V> valueConstraint = null;
    int initialCapacity = 16;
    float loadFactor = 0.75f;
    int concurrencyLevel = DEFAULT_CONCURRENCY_LEVEL;
    boolean concurrencyChecksEnabled = true;
    boolean earlyAck = false;
    boolean publisher = false;
    boolean enableAsyncConflation = false;
    boolean enableSubscriptionConflation = false;
    DiskWriteAttributes diskWriteAttributes = DiskWriteAttributesImpl.getDefaultSyncInstance();
    File[] diskDirs = DefaultDiskDirs.getDefaultDiskDirs();
    int[] diskSizes = DiskStoreFactory.DEFAULT_DISK_DIR_SIZES;
    boolean indexMaintenanceSynchronous = true;
    PartitionAttributes partitionAttributes = null;
    MembershipAttributes membershipAttributes = new MembershipAttributes();
    SubscriptionAttributes subscriptionAttributes = new SubscriptionAttributes();
    boolean multicastEnabled = false;
    EvictionAttributesImpl evictionAttributes = new EvictionAttributesImpl();
    String poolName = null;
    String diskStoreName = null;
    boolean diskSynchronous = DEFAULT_DISK_SYNCHRONOUS;
    protected boolean isBucketRegion = false;
    private boolean isCloningEnabled = false;
    Compressor compressor = null;

    boolean offHeap = false;

    /**
     * Constructs an instance of {@code RegionAttributes} with default settings.
     *
     * @see AttributesFactory
     */
    public RegionAttributesImpl() {}

    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(1000);
      buf.append("RegionAttributes@").append(System.identityHashCode(this)).append(": ")
          .append("scope=").append(scope).append("; earlyAck=").append(earlyAck)
          .append("; publisher=").append(publisher).append("; partitionAttrs=")
          .append(partitionAttributes).append("; membershipAttrs=").append(membershipAttributes)
          .append("; subscriptionAttrs=").append(subscriptionAttributes).append("; regionTTL=")
          .append(regionTimeToLive).append("; action=").append(regionTimeToLiveExpirationAction)
          .append("; regionIdleTimeout=").append(regionIdleTimeout).append("; action=")
          .append(regionIdleTimeoutExpirationAction).append("; TTL=").append(entryTimeToLive)
          .append("; action=").append(entryTimeToLiveExpirationAction).append("; custom=")
          .append(customEntryTimeToLive).append("; idleTimeout=").append(entryIdleTimeout)
          .append("; action=").append(entryIdleTimeoutExpirationAction).append("; custom=")
          .append(customEntryIdleTimeout).append("; dataPolicy=").append(dataPolicy)
          .append("; statisticsEnabled=").append(statisticsEnabled).append("; ignoreJTA=")
          .append(ignoreJTA).append("; isLockGrantor=").append(isLockGrantor)
          .append("; keyConstraint=").append(keyConstraint).append("; valueConstraint=")
          .append(valueConstraint).append("; initialCapacity=").append(initialCapacity)
          .append("; loadFactor=").append(loadFactor).append("; concurrencyLevel=")
          .append(concurrencyLevel).append("; concurrencyChecksEnabled=")
          .append(concurrencyChecksEnabled).append("; enableAsyncConflation=")
          .append(enableAsyncConflation).append("; enableSubscriptionConflation=")
          .append(enableSubscriptionConflation).append("; isBucketRegion=").append(isBucketRegion)
          .append("; poolName=").append(poolName).append("; diskSynchronous=")
          .append(diskSynchronous).append("; multicastEnabled=").append(multicastEnabled)
          .append("; isCloningEnabled=").append(isCloningEnabled);
      if (hasDiskWriteAttributes() || hasDiskDirs()) {
        buf.append("; diskAttrs=").append(diskWriteAttributes).append("; diskDirs=")
            .append(Arrays.toString(diskDirs)).append("; diskDirSizes=")
            .append(Arrays.toString(diskSizes));
      } else {
        buf.append("; diskStoreName=").append(diskStoreName);
      }
      buf.append("; GatewaySenderIds=").append(gatewaySenderIds);
      buf.append("; AsyncEventQueueIds=").append(asyncEventQueueIds);
      buf.append("; compressor=")
          .append(compressor == null ? null : compressor.getClass().getName());
      buf.append("; offHeap=").append(offHeap);
      return buf.toString();
    }

    @Override
    public CacheLoader<K, V> getCacheLoader() {
      return cacheLoader;
    }

    @Override
    public CacheWriter<K, V> getCacheWriter() {
      return cacheWriter;
    }

    @Override
    public Class<K> getKeyConstraint() {
      return keyConstraint;
    }

    @Override
    public Class<V> getValueConstraint() {
      return valueConstraint;
    }

    private boolean isForBucketRegion() {
      return isBucketRegion;
    }

    @Override
    public ExpirationAttributes getRegionTimeToLive() {
      return new ExpirationAttributes(regionTimeToLive, regionTimeToLiveExpirationAction);
    }

    @Override
    public ExpirationAttributes getRegionIdleTimeout() {
      return new ExpirationAttributes(regionIdleTimeout,
          regionIdleTimeoutExpirationAction);
    }

    @Override
    public ExpirationAttributes getEntryTimeToLive() {
      return new ExpirationAttributes(entryTimeToLive, entryTimeToLiveExpirationAction);
    }

    @Override
    public CustomExpiry<K, V> getCustomEntryTimeToLive() {
      return customEntryTimeToLive;
    }

    @Override
    public ExpirationAttributes getEntryIdleTimeout() {
      return new ExpirationAttributes(entryIdleTimeout, entryIdleTimeoutExpirationAction);
    }

    @Override
    public CustomExpiry<K, V> getCustomEntryIdleTimeout() {
      return customEntryIdleTimeout;
    }

    @Override
    public MirrorType getMirrorType() {
      if (dataPolicy.isNormal() || dataPolicy.isPreloaded() || dataPolicy.isEmpty()
          || dataPolicy.withPartitioning()) {
        return MirrorType.NONE;
      } else if (dataPolicy.withReplication()) {
        return MirrorType.KEYS_VALUES;
      } else {
        throw new IllegalStateException(
            String.format("No mirror type corresponds to data policy %s.",
                dataPolicy));
      }
    }

    @Override
    public DataPolicy getDataPolicy() {
      return dataPolicy;
    }

    public void setDataPolicy(DataPolicy dp) {
      dataPolicy = dp;
      setHasDataPolicy(true);
    }

    @Override
    public Scope getScope() {
      return scope;
    }

    public void setScope(Scope s) {
      scope = s;
      setHasScope(true);
    }

    @Immutable
    private static final CacheListener<?, ?>[] EMPTY_LISTENERS = new CacheListener[0];

    @Override
    @SuppressWarnings("unchecked")
    public CacheListener<K, V>[] getCacheListeners() {
      synchronized (this) {
        if (cacheListeners == null || cacheListeners.isEmpty()) {
          return (CacheListener<K, V>[]) EMPTY_LISTENERS;
        } else {
          CacheListener<K, V>[] result = new CacheListener[cacheListeners.size()];
          cacheListeners.toArray(result);
          return result;
        }
      }
    }

    @Override
    public CacheListener<K, V> getCacheListener() {
      synchronized (this) {
        if (cacheListeners == null) {
          return null;
        }
        if (cacheListeners.size() == 0) {
          return null;
        }
        if (cacheListeners.size() == 1) {
          return cacheListeners.get(0);
        }
      }
      throw new IllegalStateException(
          "More than one cache listener exists.");
    }

    protected void addCacheListener(CacheListener<K, V> aListener) {
      synchronized (this) {
        if (cacheListeners == null) {
          cacheListeners = new ArrayList<>(1);
          cacheListeners.add(aListener);
        } else {
          cacheListeners.add(aListener);
        }
        setHasCacheListeners(true);
      }
    }

    public void addGatewaySenderId(String gatewaySenderId) {
      synchronized (this) {
        if (gatewaySenderIds == null) {
          gatewaySenderIds = new CopyOnWriteArraySet<>();
          gatewaySenderIds.add(gatewaySenderId);
        } else {
          if (gatewaySenderIds.contains(gatewaySenderId)) {
            throw new IllegalArgumentException(
                String.format("gateway-sender-id %s is already added",
                    gatewaySenderId));
          }
          gatewaySenderIds.add(gatewaySenderId);
        }
        setHasGatewaySenderIds(true);
      }
    }

    public void addAsyncEventQueueId(String asyncEventQueueId) {
      synchronized (this) {
        if (asyncEventQueueIds == null) {
          asyncEventQueueIds = new CopyOnWriteArraySet<>();
          asyncEventQueueIds.add(asyncEventQueueId);
        } else {
          if (asyncEventQueueIds.contains(asyncEventQueueId)) {
            throw new IllegalArgumentException(
                String.format("async-event-queue-id %s is already added",
                    asyncEventQueueId));
          }
          asyncEventQueueIds.add(asyncEventQueueId);
        }
        setHasAsyncEventListeners(true);
      }
    }

    @Override
    public int getInitialCapacity() {
      return initialCapacity;
    }

    @Override
    public float getLoadFactor() {
      return loadFactor;
    }

    @Override
    public boolean getStatisticsEnabled() {
      return statisticsEnabled;
    }

    @Override
    public boolean getIgnoreJTA() {
      return ignoreJTA;
    }

    @Override
    public boolean isLockGrantor() {
      return isLockGrantor;
    }

    @Override
    public int getConcurrencyLevel() {
      return concurrencyLevel;
    }

    @Override
    public boolean getConcurrencyChecksEnabled() {
      return concurrencyChecksEnabled;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    @Override
    public Object clone() {
      try {
        RegionAttributesImpl<K, V> copy = (RegionAttributesImpl<K, V>) super.clone();
        if (copy.getIndexes() != null) {
          copy.setIndexes(new ArrayList(copy.getIndexes()));
        }
        if (copy.partitionAttributes != null) {
          copy.partitionAttributes = ((PartitionAttributesImpl) copy.partitionAttributes).copy();
        }
        if (copy.cacheListeners != null) {
          copy.cacheListeners = new ArrayList<>(copy.cacheListeners);
        }
        if (copy.gatewaySenderIds != null) {
          copy.gatewaySenderIds = new CopyOnWriteArraySet<>(copy.gatewaySenderIds);
        }
        if (copy.asyncEventQueueIds != null) {
          copy.asyncEventQueueIds = new CopyOnWriteArraySet<>(copy.asyncEventQueueIds);
        }
        return copy;
      } catch (CloneNotSupportedException e) {
        throw new InternalError(
            "CloneNotSupportedException thrown in class that implements cloneable.");
      }
    }

    @Override
    public boolean getPersistBackup() {
      return getDataPolicy().withPersistence();
    }

    @Override
    public boolean getEarlyAck() {
      return earlyAck;
    }

    /*
     * @deprecated as of 6.5
     */
    @Override
    @Deprecated
    public boolean getPublisher() {
      return publisher;
    }

    @Override
    public boolean getEnableConflation() { // deprecated in 5.0
      return getEnableSubscriptionConflation();
    }

    @Override
    public boolean getEnableAsyncConflation() {
      return enableAsyncConflation;
    }

    @Override
    public boolean getEnableBridgeConflation() { // deprecated in 5.7
      return getEnableSubscriptionConflation();
    }

    @Override
    public boolean getEnableSubscriptionConflation() {
      return enableSubscriptionConflation;
    }

    /**
     * @deprecated as of 6.5
     */
    @Override
    @Deprecated
    public DiskWriteAttributes getDiskWriteAttributes() {
      if (diskStoreName != null) {
        throw new IllegalStateException(
            String.format("Deprecated API %s cannot be used with DiskStore %s",
                "getDiskWriteAttributes", diskStoreName));
      }
      return diskWriteAttributes;
    }

    /**
     * @deprecated as of 6.5
     */
    @Override
    @Deprecated
    public File[] getDiskDirs() {
      if (diskStoreName != null) {
        throw new IllegalStateException(
            String.format("Deprecated API %s cannot be used with DiskStore %s",
                "getDiskDirs", diskStoreName));
      }
      return diskDirs;
    }

    @Override
    public boolean getIndexMaintenanceSynchronous() {
      return indexMaintenanceSynchronous;
    }

    @Override
    public PartitionAttributes getPartitionAttributes() {
      return partitionAttributes;
    }

    @Override
    public EvictionAttributes getEvictionAttributes() {
      return evictionAttributes;
    }

    /**
     * @deprecated this API is scheduled to be removed
     */
    @Override
    @Deprecated
    public MembershipAttributes getMembershipAttributes() {
      return membershipAttributes;
    }

    @Override
    public SubscriptionAttributes getSubscriptionAttributes() {
      return subscriptionAttributes;
    }

    /**
     * @deprecated as of 6.5
     */
    @Override
    @Deprecated
    public int[] getDiskDirSizes() {
      if (diskStoreName != null) {
        throw new IllegalStateException(
            String.format("Deprecated API %s cannot be used with DiskStore %s",
                "getDiskDirSizes", diskStoreName));
      }
      return diskSizes;
    }

    @Override
    public String getDiskStoreName() {
      return diskStoreName;
    }

    @Override
    public boolean getMulticastEnabled() {
      return multicastEnabled;
    }

    @Override
    public String getPoolName() {
      return poolName;
    }

    @Override
    public boolean getCloningEnabled() {
      return isCloningEnabled;
    }

    @Override
    public boolean isDiskSynchronous() {
      return diskSynchronous;
    }

    @Override
    public Set<String> getGatewaySenderIds() {
      if (!hasGatewaySenderId()) {
        gatewaySenderIds = new CopyOnWriteArraySet<>();
      }
      return gatewaySenderIds;
    }

    @Override
    public Set<String> getAsyncEventQueueIds() {
      if (!hasAsyncEventListeners()) {
        asyncEventQueueIds = new CopyOnWriteArraySet<>();
      }
      return asyncEventQueueIds;
    }

    @Override
    public Compressor getCompressor() {
      return compressor;
    }

    @Override
    public boolean getOffHeap() {
      return offHeap;
    }
  }
}
