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 static boolean isStartRemoveExpiredThread = false; 062 private static 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 * Start remove expired thread 082 * */ 083 private synchronized void startRemoveExpiredThread() { 084 isStartRemoveExpiredThread = true; 085 executor = Executors.newFixedThreadPool(1); 086 executor.execute(new Runnable() { 087 @Override 088 public void run() { 089 Thread.currentThread().setName("MemoryCache"); 090 while (true) { 091 092 if (Thread.currentThread().isInterrupted()) { 093 break; 094 } 095 096 Set<String> names = CACHE_LIFE.keySet(); 097 for (String name : names) { 098 Date current = new Date(); 099 Date life = CACHE_LIFE.get(name); 100 if (life != null) { 101 if (current.getTime() > life.getTime()) { 102 remove(name); 103 log.debug("Removed(Expired) -> {}", name); 104 } 105 } 106 } 107 try { 108 Thread.sleep(TIMER); 109 } catch (InterruptedException e) { 110 log.warn(e); 111 Thread.currentThread().interrupted(); 112 } 113 } 114 } 115 116 }); 117 } 118 119 @Override 120 synchronized boolean included(String name) { 121 if (stored) { 122 return CACHE_VALUE.containsKey(name); 123 }else{ 124 if (CACHE_LIFE.containsKey(name)) { 125 Date current = new Date(); 126 Date life = CACHE_LIFE.get(name); 127 if (life != null) { 128 if (current.getTime() > life.getTime()) { 129 return false; 130 } else { 131 return true; 132 } 133 } 134 return false; 135 } else { 136 return false; 137 } 138 } 139 } 140 141 @Override 142 synchronized List<String> getNames() { 143 Set<String> names = CACHE_VALUE.keySet(); 144 if (names != null) { 145 return new ArrayList<String>(names); 146 } else { 147 return null; 148 } 149 } 150 151 @Override 152 synchronized void removeAll() { 153 Set<String> names = CACHE_VALUE.keySet(); 154 for (String name : names) { 155 remove(name); 156 } 157 } 158 159 @Override 160 synchronized void remove(String name) { 161 if (CACHE_VALUE.containsKey(name)) { 162 CACHE_LIFE.remove(name); 163 CACHE_VALUE.remove(name); 164 } 165 } 166 167 @Override 168 synchronized Object get(String name) { 169 try { 170 if (included(name)) { 171 Object cacheObjectValue = CACHE_VALUE.get(name); 172 return cacheObjectValue; 173 } else { 174 return null; 175 } 176 } catch (Exception e) { 177 log.warn(e); 178 return null; 179 } 180 } 181 182 @Override 183 synchronized void set(String name, Object value, int lifeSeconds) { 184 if(value == null) return ; 185 186 if(lifeSeconds <= 0){ 187 log.debug("Skip use cache, key '{}' life is '{}s'",name,lifeSeconds); 188 return; 189 } 190 191 try { 192 CACHE_VALUE.put(name, value); 193 if(!stored){ 194 Date life = new Date(new Date().getTime() + lifeSeconds * 1000); 195 CACHE_LIFE.put(name, life); 196 } 197 } catch (Exception e) { 198 log.warn(e); 199 } 200 201 if (!isStartRemoveExpiredThread) 202 startRemoveExpiredThread(); 203 } 204 205}