/* * Copyright 2016, Stuart Douglas, and individual contributors as indicated * by the @authors tag. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fakereplace.transformation; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import org.fakereplace.core.AgentOption; import org.fakereplace.core.AgentOptions; import org.fakereplace.logging.Logger; /** * Class that tracks unmodified files that can be ignored on future boots. This provides a big speed improvement, * as it means that only classes that actually have to be modified are parsed by javassist. * * @author Stuart Douglas */ public class UnmodifiedFileIndex { private static String VERSION = "1.0"; private static final Logger log = Logger.getLogger(UnmodifiedFileIndex.class); private static final Set<String> index = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static Timer writeTimer = null; public static void loadIndex() { final File file = getFile(); if(file == null) { return; } if (file.exists() && !file.isDirectory()) { log.debug("Reading Fakereplace unmodified class cache from " + file.getAbsolutePath()); try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))){ final String version = reader.readLine(); if (VERSION.equals(version)) { String line = reader.readLine(); while (line != null) { index.add(line); line = reader.readLine(); } } } catch (IOException e) { log.error("Failed to load unmodified file index", e); } } final Thread writerThread = new Thread(() -> { synchronized (UnmodifiedFileIndex.class) { writeIndex(); } }); Runtime.getRuntime().addShutdownHook(writerThread); } private static void writeIndex() { final File file = getFile(); if(file == null) { return; } log.debug("Writing Fakereplace unmodified class cache at " + file.getAbsolutePath()); if (!file.isDirectory()) { if(file.getParentFile() != null && !file.getParentFile().exists()) { file.getParentFile().mkdirs(); } try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)){ writer.write(VERSION); writer.write('\n'); //there is no real need to sort //but it makes the file easier to examine when debugging final List<String> sortedIndex = new ArrayList<>(index); Collections.sort(sortedIndex); for (String clazz : sortedIndex) { writer.write(clazz); writer.write('\n'); } } catch (IOException e) { log.error("Failed to write unmodified file index", e); } } } private static File getFile() { String noIndex = AgentOptions.getOption(AgentOption.NO_INDEX); if(noIndex != null && Boolean.parseBoolean(noIndex)) { return null; } final String fileProp = AgentOptions.getOption(AgentOption.INDEX_FILE); return new File(fileProp); } static synchronized void markClassUnmodified(final String clazz) { index.add(clazz); if(writeTimer == null && getFile() != null) { //the shutdown hook is not always reliable, so we write the index every 10 seconds //but only if new classes are added to it writeTimer = new Timer("Fakereplace index writing timer", true); writeTimer.schedule(new TimerTask() { @Override public void run() { synchronized (UnmodifiedFileIndex.class) { writeIndex(); writeTimer.cancel(); writeTimer = null; } } }, 10000); } } static boolean isClassUnmodified(final String clazz) { return index.contains(clazz); } }