001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.cache;
025
026import com.killcoding.cache.AbsCacheApi;
027import com.killcoding.tool.CommonTools;
028import java.util.Date;
029import java.util.Map;
030import java.util.concurrent.ConcurrentHashMap;
031import java.nio.file.Files;
032import java.io.File;
033import java.nio.file.Path;
034import java.util.stream.Stream;
035import java.util.Comparator;
036import java.nio.file.Paths;
037import com.killcoding.log.Logger;
038import com.killcoding.log.LoggerFactory;
039import com.killcoding.tool.FileTools;
040import java.util.Set;
041import java.util.Arrays;
042import java.util.List;
043import java.util.ArrayList;
044import java.util.concurrent.ExecutorService;
045import java.util.concurrent.Executors;
046import java.util.concurrent.Future;
047import java.text.SimpleDateFormat;
048
049/**
050 * This cache class is save to memory
051 * */
052public class MemoryCache extends AbsCacheApi {
053
054        private static final Logger log = LoggerFactory.getLogger(MemoryCache.class);
055
056        private final long PID = ProcessHandle.current().pid();
057
058        private final static Map<String, Object> CACHE_VALUE = new ConcurrentHashMap<String, Object>();
059        private final static Map<String, Date> CACHE_LIFE = new ConcurrentHashMap<String, Date>();
060
061        private boolean isStartRemoveExpiredThread = false;
062        private ExecutorService executor = null;
063        
064    /**
065    * New a MemoryCache object
066    * */
067        public MemoryCache() {
068                super();
069        }
070
071    /**
072    * New a MemoryCache object
073    * @param stored - 'true' is force write cache
074    * */
075        public MemoryCache(Boolean stored) {
076                super();
077                this.stored = stored;
078        }
079
080        /**
081         * Shutdown cahee thread
082         * */   
083        public synchronized void shutdown(){
084            removeAll();
085            if(executor != null)
086                executor.shutdown();
087        }       
088
089    /**
090     * Start remove expired thread
091     * */
092        private synchronized void startRemoveExpiredThread() {
093                isStartRemoveExpiredThread = true;
094                executor = Executors.newFixedThreadPool(1);
095                executor.execute(new Runnable() {
096                        @Override
097                        public void run() {
098                                Thread.currentThread().setName("MemoryCache");
099                                while (true) {
100
101                                        if (Thread.currentThread().isInterrupted()) {
102                                                break;
103                                        }
104
105                                        Set<String> names = CACHE_LIFE.keySet();
106                                        for (String name : names) {
107                                                Date current = new Date();
108                                                Date life = CACHE_LIFE.get(name);
109                                                if (life != null) {
110                                                if (current.getTime() > life.getTime()) {
111                                                        remove(name);
112                                                        log.debug("Removed(Expired) -> {}", name);
113                                                }
114                                                }
115                                        }
116                                        try {
117                                                Thread.sleep(TIMER);
118                                        } catch (InterruptedException e) {
119                                                log.warn(e);
120                                                Thread.currentThread().interrupted();
121                                        }
122                                }
123                        }
124
125                });
126        }
127
128        @Override
129        synchronized boolean included(String name) {
130                if (stored) {
131                        return CACHE_VALUE.containsKey(name);
132                }else{
133                        if (CACHE_LIFE.containsKey(name)) {
134                                Date current = new Date();
135                                Date life = CACHE_LIFE.get(name);
136                                if (life != null) {
137                                if (current.getTime() > life.getTime()) {
138                                        return false;
139                                } else {
140                                        return true;
141                                }
142                                }
143                                return false;
144                        } else {
145                                return false;
146                        }
147                }
148        }
149
150        @Override
151        synchronized List<String> getNames() {
152                Set<String> names = CACHE_VALUE.keySet();
153                if (names != null) {
154                        return new ArrayList<String>(names);
155                } else {
156                        return null;
157                }
158        }
159
160        @Override
161        synchronized void removeAll() {
162                Set<String> names = CACHE_VALUE.keySet();
163                for (String name : names) {
164                        remove(name);
165                }
166        }
167
168        @Override
169        synchronized void remove(String name) {
170                if (CACHE_VALUE.containsKey(name)) {
171                        CACHE_LIFE.remove(name);
172                        CACHE_VALUE.remove(name);
173                }
174        }
175
176        @Override
177        synchronized Object get(String name) {
178                try {
179                        if (included(name)) {
180                                Object cacheObjectValue = CACHE_VALUE.get(name);
181                                return cacheObjectValue;
182                        } else {
183                                return null;
184                        }
185                } catch (Exception e) {
186                        log.warn(e);
187                        return null;
188                }
189        }
190
191        @Override
192        synchronized void set(String name, Object value, int lifeSeconds) {
193            if(value == null) return ;
194            
195            if(lifeSeconds <= 0){
196                log.debug("Skip use cache, key '{}' life is '{}s'",name,lifeSeconds);
197                return;    
198            }
199            
200                try {
201                        CACHE_VALUE.put(name, value);
202                        if(!stored){
203                            Date life = new Date(new Date().getTime() + lifeSeconds * 1000);
204                            CACHE_LIFE.put(name, life);
205                    }
206                } catch (Exception e) {
207                        log.warn(e);
208                }
209
210                if (!isStartRemoveExpiredThread)
211                        startRemoveExpiredThread();
212        }
213
214}