class Cache implements InternalEhcache, StoreListener {

    public void initialise() {
        synchronized (this) {
            if (!cacheStatus.canInitialize()) {
                throw new IllegalStateException("Cannot initialise the " + configuration.getName()
                        + " cache because its status is not STATUS_UNINITIALISED");
            }

            // on-heap pool configuration
            Pool onHeapPool;
            if (configuration.getMaxBytesLocalHeap() > 0) {
                PoolEvictor<PoolableStore> evictor = new FromLargestCacheOnHeapPoolEvictor();
                SizeOfEngine sizeOfEngine = cacheManager.createSizeOfEngine(this);
                onHeapPool = new BoundedPool(configuration.getMaxBytesLocalHeap(), evictor, sizeOfEngine);
            } else if (getCacheManager() != null && getCacheManager().getConfiguration().isMaxBytesLocalHeapSet()) {
                onHeapPool = getCacheManager().getOnHeapPool();
            } else {
                onHeapPool = new UnboundedPool();
            }

            // on-disk pool configuration
            Pool onDiskPool;
            if (configuration.getMaxBytesLocalDisk() > 0) {
                PoolEvictor<PoolableStore> evictor = new FromLargestCacheOnDiskPoolEvictor();
                onDiskPool = new BoundedPool(configuration.getMaxBytesLocalDisk(), evictor, null);
            } else if (getCacheManager() != null && getCacheManager().getConfiguration().isMaxBytesLocalDiskSet()) {
                onDiskPool = getCacheManager().getOnDiskPool();
            } else {
                onDiskPool = new UnboundedPool();
            }

            ReadWriteCopyStrategy<Element> copyStrategy = null;
            if (configuration.getTransactionalMode().isTransactional()) {
                configuration.getCopyStrategyConfiguration().setCopyStrategyInstance(null);
                copyStrategy = configuration.getCopyStrategyConfiguration().getCopyStrategyInstance();
                configuration.getCopyStrategyConfiguration().setCopyStrategyInstance(new ImmutableValueElementCopyStrategy());
            }
            elementValueComparator = configuration.getElementValueComparatorConfiguration().getElementComparatorInstance(configuration);

            if (configuration.getTransactionalMode().isTransactional()
                && configuration.isTerracottaClustered()
                && configuration.getTerracottaConfiguration().getValueMode() != TerracottaConfiguration.ValueMode.SERIALIZATION) {
                throw new CacheException("To be transactional, a Terracotta clustered cache needs to be in Serialization value mode");
            }

            Store store;
            if (isTerracottaClustered()) {
                checkClusteredConfig();
                int maxConcurrency = Integer.getInteger(EHCACHE_CLUSTERREDSTORE_MAX_CONCURRENCY_PROP,
                        DEFAULT_EHCACHE_CLUSTERREDSTORE_MAX_CONCURRENCY);
                if (getCacheConfiguration().getTerracottaConfiguration().getConcurrency() > maxConcurrency) {
                    throw new InvalidConfigurationException("Maximum supported concurrency for Terracotta clustered caches is "
                            + maxConcurrency + ". Please reconfigure cache '" + getName() + "' with concurrency value <= " + maxConcurrency
                            + " or use system property '" + EHCACHE_CLUSTERREDSTORE_MAX_CONCURRENCY_PROP + "' to override the default");
                }
                boolean cachePinned = getCacheConfiguration().getPinningConfiguration() != null
                        && getCacheConfiguration().getPinningConfiguration().getStore() == PinningConfiguration.Store.INCACHE;
                if (!cachePinned && getCacheConfiguration().getMaxElementsOnDisk() == 0) {
                    LOG.warn("Performance may degrade and server disks could run out of space!\nThe distributed cache {} does not have " +
                             "maxElementsOnDisk set. Failing to set maxElementsOnDisk could mean no eviction of its elements from the " +
                             "Terracotta Server Array disk store. To avoid this, set maxElementsOnDisk to a non-zero value.", getName());
                }
                Store tempStore = cacheManager.createTerracottaStore(this);
                if (!(tempStore instanceof TerracottaStore)) {
                    throw new CacheException(
                            "CacheManager should create instances of TerracottaStore for Terracotta Clustered caches instead of - "
                                    + (tempStore == null ? "null" : tempStore.getClass().getName()));
                }
                CacheConfiguration.TransactionalMode clusteredTransactionalMode = ((TerracottaStore)tempStore).getTransactionalMode();
                if (clusteredTransactionalMode != null && !clusteredTransactionalMode.equals(configuration.getTransactionalMode())) {
                    throw new InvalidConfigurationException("Transactional mode cannot be changed on clustered caches. "
                        + "Please reconfigure cache '" + getName() + "' with transactionalMode = " + clusteredTransactionalMode
                    );
                }
                TerracottaStore terracottaStore = (TerracottaStore) makeXaStrictTransactionalIfNeeded(tempStore, copyStrategy);

                NonstopConfiguration nonstopConfig = getCacheConfiguration().getTerracottaConfiguration().getNonstopConfiguration();
                // freeze the config whether nonstop is enabled or not
                if (nonstopConfig != null) {
                    nonstopConfig.freezeConfig();
                }
                if (getCacheConfiguration().getTerracottaConfiguration().isNonstopEnabled()) {
                    nonstopActiveDelegateHolder.terracottaStoreInitialized(terracottaStore);
                    store = nonstopActiveDelegateHolder.getNonstopStore();
                } else {
                    store = terracottaStore;
                }
            } else {
                FeaturesManager featuresManager = cacheManager.getFeaturesManager();
                if (featuresManager == null) {
                    if (configuration.isOverflowToOffHeap()) {
                        throw new CacheException("Cache " + configuration.getName()
                                + " cannot be configured because the enterprise features manager could not be found. "
                                + "You must use an enterprise version of Ehcache to successfully enable overflowToOffHeap.");
                    }
                    PersistenceConfiguration persistence = configuration.getPersistenceConfiguration();
                    if (persistence != null && Strategy.LOCALRESTARTABLE.equals(persistence.getStrategy())) {
                        throw new CacheException("Cache " + configuration.getName()
                                + " cannot be configured because the enterprise features manager could not be found. "
                                + "You must use an enterprise version of Ehcache to successfully enable enterprise persistence.");
                    }

                    if (useClassicLru && configuration.getMemoryStoreEvictionPolicy().equals(MemoryStoreEvictionPolicy.LRU)) {
                        Store disk = createDiskStore();
                        store = new LegacyStoreWrapper(new LruMemoryStore(this, disk), disk, registeredEventListeners, configuration);
                    } else {
                        if (configuration.isOverflowToDisk()) {
                            store = DiskBackedMemoryStore.create(this, onHeapPool, onDiskPool);
                        } else {
                            store = MemoryOnlyStore.create(this, onHeapPool);
                        }
                    }
                } else {
                    store = featuresManager.createStore(this, onHeapPool, onDiskPool);
                }
                store = makeXaStrictTransactionalIfNeeded(store, copyStrategy);
            }

            /* note: this isn't part of makeXaStrictTransactionalIfNeeded() as only xa_strict supports NonStop, meaning that only
             * that transactional store can be wrapped by NonStopStore. Other TX modes have to wrap the NonStop store due to their
             * lack of NonStop support (ie: lack of transaction context suspension/resuming).
             */
            if (configuration.isXaTransactional()) {
                SoftLockManager softLockManager = cacheManager.createSoftLockManager(this);
                LocalTransactionStore localTransactionStore = new LocalTransactionStore(getCacheManager().getTransactionController(),
                        getCacheManager().getOrCreateTransactionIDFactory(), softLockManager, this, store, copyStrategy);
                this.compoundStore = new JtaLocalTransactionStore(localTransactionStore, transactionManagerLookup,
                        cacheManager.getTransactionController());
            } else if (configuration.isLocalTransactional()) {
                SoftLockManager softLockManager = cacheManager.createSoftLockManager(this);
                this.compoundStore = new LocalTransactionStore(getCacheManager().getTransactionController(),
                        getCacheManager().getOrCreateTransactionIDFactory(), softLockManager, this, store, copyStrategy);
            } else {
                this.compoundStore = store;
            }

            if (isSearchable()) {
                this.compoundStore = new ElementIdAssigningStore(compoundStore);
            }

            Map<String, AttributeExtractor> extractors = new HashMap<String, AttributeExtractor>();
            for (SearchAttribute sa : configuration.getSearchAttributes().values()) {
                extractors.put(sa.getName(), sa.constructExtractor());
            }
            compoundStore.setAttributeExtractors(extractors);
            this.cacheWriterManager = configuration.getCacheWriterConfiguration().getWriteMode().createWriterManager(this);
            cacheStatus.changeState(Status.STATUS_ALIVE);
            initialiseRegisteredCacheWriter();
            initialiseCacheWriterManager(false);
            initialiseRegisteredCacheExtensions();
            initialiseRegisteredCacheLoaders();
            // initialize live statistics
            // register to get notifications of
            // put/update/removeInternal/expiry/eviction
            getCacheEventNotificationService().registerListener(liveCacheStatisticsData);
            // set up default values
            liveCacheStatisticsData.setStatisticsAccuracy(Statistics.STATISTICS_ACCURACY_BEST_EFFORT);
            liveCacheStatisticsData.setStatisticsEnabled(configuration.getStatistics());

            // register the sampled cache statistics
            this.registerCacheUsageListener(sampledCacheStatistics);

            if (isTerracottaClustered()) {
                // create this to be sure that it's present on each node to receive clustered events,
                // even if this node is not sending out its events
                cacheManager.createTerracottaEventReplicator(this);
            }

            Object context = compoundStore.getInternalContext();
            if (context instanceof CacheLockProvider) {
                lockProvider = (CacheLockProvider) context;
            } else {
                this.lockProvider = new StripedReadWriteLockSync(StripedReadWriteLockSync.DEFAULT_NUMBER_OF_MUTEXES);
            }
        }

        compoundStore.addStoreListener(this);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Initialised cache: " + configuration.getName());
        }

        if (disabled) {
            LOG.warn("Cache: " + configuration.getName() + " is disabled because the " + NET_SF_EHCACHE_DISABLED
                    + " property was set to true. No elements will be added to the cache.");
        }
    }

    public  Cache(CacheConfiguration cacheConfiguration);
    public  Cache(CacheConfiguration cacheConfiguration,
                 RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader);
    public  Cache(String name, int maxElementsInMemory, boolean overflowToDisk,
                 boolean eternal, long timeToLiveSeconds, long timeToIdleSeconds);
    public  Cache(String name,
                 int maxElementsInMemory,
                 boolean overflowToDisk,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds);
    public  Cache(String name,
                 int maxElementsInMemory,
                 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
                 boolean overflowToDisk,
                 String diskStorePath,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds,
                 RegisteredEventListeners registeredEventListeners);
    public  Cache(String name,
                 int maxElementsInMemory,
                 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
                 boolean overflowToDisk,
                 String diskStorePath,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds,
                 RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader);
    public  Cache(String name,
                 int maxElementsInMemory,
                 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
                 boolean overflowToDisk,
                 String diskStorePath,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds,
                 RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader,
                 int maxElementsOnDisk);
    public  Cache(String name,
                 int maxElementsInMemory,
                 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
                 boolean overflowToDisk,
                 String diskStorePath,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds,
                 RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader,
                 int maxElementsOnDisk,
                 int diskSpoolBufferSizeMB);
    public  Cache(String name,
                 int maxElementsInMemory,
                 MemoryStoreEvictionPolicy memoryStoreEvictionPolicy,
                 boolean overflowToDisk,
                 String diskStorePath,
                 boolean eternal,
                 long timeToLiveSeconds,
                 long timeToIdleSeconds,
                 boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds,
                 RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader,
                 int maxElementsOnDisk,
                 int diskSpoolBufferSizeMB,
                 boolean clearOnFlush);
    public  Cache(String name, int maxElementsInMemory, MemoryStoreEvictionPolicy memoryStoreEvictionPolicy, boolean overflowToDisk,
                 String diskStorePath, boolean eternal, long timeToLiveSeconds, long timeToIdleSeconds, boolean diskPersistent,
                 long diskExpiryThreadIntervalSeconds, RegisteredEventListeners registeredEventListeners,
                 BootstrapCacheLoader bootstrapCacheLoader, int maxElementsOnDisk, int diskSpoolBufferSizeMB, boolean clearOnFlush,
                 boolean isTerracottaClustered, String terracottaValueMode, boolean terracottaCoherentReads);

}