Java tutorial
/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package com.l2jfree.gameserver.cache; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import javolution.util.FastMap; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.l2jfree.Config; import com.l2jfree.L2AutoInitialization; import com.l2jfree.gameserver.network.packets.server.AbstractNpcHtmlMessage; import com.l2jfree.gameserver.util.Util; /** * @authors Layane, nbali, savormix, hex1r0 */ public final class HtmCache { private static final Log _log = LogFactory.getLog(HtmCache.class); private static final FileFilter HTM_FILTER = file -> { if (L2AutoInitialization.isIDEMode() && file.toURI().getPath().contains("target/classes")) { return false; } return file.isDirectory() || file.getName().endsWith(".htm") || file.getName().endsWith(".html"); }; public static HtmCache getInstance() { return SingletonHolder._instance; } private FastMap<String, String> _cache = new FastMap<String, String>(16000); private int _loadedFiles; private int _size; private HtmCache() { if (!Config.ALT_DEV_NO_HTMLS) reload(false); } @SuppressWarnings("unchecked") public synchronized void reload(boolean deleteCacheFile) { _cache.clear(); _loadedFiles = 0; _size = 0; final File cacheFile = getCacheFile(); if (deleteCacheFile && cacheFile.exists()) { _log.info("Cache[HTML]: Deleting cache file..."); cacheFile.delete(); } _log.info("Cache[HTML]: Caching started..."); if (cacheFile.exists()) { _log.info("Cache[HTML]: Using cache file..."); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(getCacheFile()))); _cache = (FastMap<String, String>) ois.readObject(); for (String html : _cache.values()) { _loadedFiles++; _size += html.length(); } } catch (Exception e) { _log.warn("", e); reload(true); return; } finally { IOUtils.closeQuietly(ois); } } else { parseDir(Config.DATAPACK_ROOT); } _log.info(this); if (cacheFile.exists()) { _log.info("Cache[HTML]: Compaction skipped!"); } else { _log.info("Cache[HTML]: Compacting htmls..."); final StringBuilder sb = new StringBuilder(8192); for (Entry<String, String> entry : _cache.entrySet()) { try { final String oldHtml = entry.getValue(); final String newHtml = compactHtml(sb, oldHtml); _size -= oldHtml.length(); _size += newHtml.length(); entry.setValue(newHtml); } catch (RuntimeException e) { _log.warn("Cache[HTML]: Error during compaction of " + entry.getKey(), e); } } _log.info(this); } if (cacheFile.exists()) { _log.info("Cache[HTML]: Validation skipped!"); } else { _log.info("Cache[HTML]: Validating htmls..."); validate(); } if (!cacheFile.exists()) { _log.info("Cache[HTML]: Creating cache file..."); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(getCacheFile()))); oos.writeObject(_cache); } catch (IOException e) { _log.warn("", e); } finally { IOUtils.closeQuietly(oos); } } } private File getCacheFile() { return new File(Config.HTML_CACHE_FILE); } private void validate() { final Set<String> set = new HashSet<String>(); for (Entry<String, String> entry : _cache.entrySet()) { final String filename = entry.getKey(); final String html = entry.getValue(); outer: for (int begin = 0; (begin = html.indexOf("<", begin)) != -1; begin++) { int end; for (end = begin; end < html.length(); end++) { if (html.charAt(end) == '>' || html.charAt(end) == ' ') break; // some special quest-replaced tag if (end == begin + 1 && html.charAt(end) == '?') continue outer; } end++; String tag = html.substring(begin + 1, end - 1).toLowerCase().replaceAll("/", ""); if (tag.contains("!--")) continue outer; for (String tag2 : AbstractNpcHtmlMessage.VALID_TAGS) if (tag.equals(tag2)) continue outer; set.add(filename + ": '" + tag + "'"); } } if (!set.isEmpty()) { _log.info("Invalid tags used: " + set.size()); for (String tag : set) _log.info(tag); } } private static final String[] TAGS_TO_COMPACT; static { // TODO: is there any other tag that should be replaced? final String[] tagsToCompact = { "html", "title", "body", "br", "br1", "p", "table", "tr", "td" }; final List<String> list = new ArrayList<String>(); for (String tag : tagsToCompact) { list.add("<" + tag + ">"); list.add("</" + tag + ">"); list.add("<" + tag + "/>"); list.add("<" + tag + " />"); } final List<String> list2 = new ArrayList<String>(); for (String tag : list) { list2.add(tag); list2.add(tag + " "); list2.add(" " + tag); } TAGS_TO_COMPACT = list2.toArray(new String[list.size()]); } private String compactHtml(StringBuilder sb, String html) { sb.setLength(0); sb.append(html); for (int i = 0; i < sb.length(); i++) if (Character.isWhitespace(sb.charAt(i))) sb.setCharAt(i, ' '); replaceAll(sb, " ", " "); replaceAll(sb, "< ", "<"); replaceAll(sb, " >", ">"); for (int i = 0; i < TAGS_TO_COMPACT.length; i += 3) { replaceAll(sb, TAGS_TO_COMPACT[i + 1], TAGS_TO_COMPACT[i]); replaceAll(sb, TAGS_TO_COMPACT[i + 2], TAGS_TO_COMPACT[i]); } replaceAll(sb, " ", " "); // String.trim() without additional garbage int fromIndex = 0; int toIndex = sb.length(); while (fromIndex < toIndex && sb.charAt(fromIndex) == ' ') fromIndex++; while (fromIndex < toIndex && sb.charAt(toIndex - 1) == ' ') toIndex--; return sb.substring(fromIndex, toIndex); } private void replaceAll(StringBuilder sb, String pattern, String value) { for (int index = 0; (index = sb.indexOf(pattern, index)) != -1;) sb.replace(index, index + pattern.length(), value); } public void reloadPath(File f) { parseDir(f); _log.info("Cache[HTML]: Reloaded specified path."); } public void parseDir(File dir) { for (File file : dir.listFiles(HTM_FILTER)) { if (!file.isDirectory()) loadFile(file); else parseDir(file); } } public String loadFile(File file) { if (isLoadable(file)) { BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(file)); byte[] raw = new byte[bis.available()]; bis.read(raw); String content = new String(raw, "UTF-8"); String relpath = Util.getRelativePath(Config.DATAPACK_ROOT, file); _size += content.length(); String oldContent = _cache.get(relpath); if (oldContent == null) _loadedFiles++; else _size -= oldContent.length(); _cache.put(relpath, content); return content; } catch (Exception e) { _log.warn("Problem with htm file:", e); } finally { IOUtils.closeQuietly(bis); } } return null; } public String getHtmForce(String path) { String content = getHtm(path); if (content == null) { content = "<html><body>My text is missing:<br>" + path + "</body></html>"; _log.warn("Cache[HTML]: Missing HTML page: " + path); } return content; } public String getHtm(String path) { return _cache.get(path); } private boolean isLoadable(File file) { return file.exists() && !file.isDirectory() && HTM_FILTER.accept(file); } public boolean pathExists(String path) { return _cache.containsKey(path); } @Override public String toString() { return "Cache[HTML]: " + String.format("%.3f", (float) _size / 1024) + " kilobytes on " + _loadedFiles + " file(s) loaded."; } @SuppressWarnings("synthetic-access") private static class SingletonHolder { protected static final HtmCache _instance = new HtmCache(); } }