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}