
org.fakereplace.integration.filewatcher.FileSystemWatcher Maven / Gradle / Ivy
/*
* 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.integration.filewatcher;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fakereplace.core.Fakereplace;
import org.fakereplace.data.ClassLoaderData;
import org.fakereplace.replacement.AddedClass;
import org.fakereplace.util.FileReader;
import org.fakereplace.util.MD5;
import javassist.bytecode.ClassFile;
/**
* Class that is responsible for watching the file system and reporting on class change events.
*
* Internally it uses {@link WatchServiceFileSystemWatcher} to watch the file system.
*
* @author Stuart Douglas
*/
class FileSystemWatcher {
private final WatchServiceFileSystemWatcher watcher = new WatchServiceFileSystemWatcher();
private final Set registered = new HashSet<>();
private final Map hashes = new HashMap<>();
private final ClassLoaderData.AttachmentKey callbackAttachmentKey = new ClassLoaderData.AttachmentKey<>();
private final class Callback implements WatchServiceFileSystemWatcher.FileChangeCallback {
private final ClassLoader classLoader;
private Callback(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void handleChanges(Collection changes) {
try {
List addedClasses = new ArrayList<>();
List changedClasses = new ArrayList<>();
for (WatchServiceFileSystemWatcher.FileChangeEvent change : changes) {
if (change.getType() == WatchServiceFileSystemWatcher.FileChangeEvent.Type.ADDED) {
try (FileInputStream in = new FileInputStream(change.getFile().toFile())) {
byte[] bytes = FileReader.readFileBytes(in);
ClassFile file = new ClassFile(new DataInputStream(new ByteArrayInputStream(bytes)));
addedClasses.add(new AddedClass(file.getName(), bytes, classLoader));
}
} else if (change.getType() == WatchServiceFileSystemWatcher.FileChangeEvent.Type.MODIFIED) {
String hash = hashes.get(change.getFile().toAbsolutePath());
if (hash == null) {
//class is not loaded yet
continue;
}
try (FileInputStream in = new FileInputStream(change.getFile().toFile())) {
byte[] bytes = FileReader.readFileBytes(in);
if (!hash.equals(MD5.md5(bytes))) {
ClassFile file = new ClassFile(new DataInputStream(new ByteArrayInputStream(bytes)));
changedClasses.add(new ClassDefinition(classLoader.loadClass(file.getName()), bytes));
}
}
}
}
Fakereplace.redefine(changedClasses.toArray(new ClassDefinition[changedClasses.size()]), addedClasses.toArray(new AddedClass[addedClasses.size()]));
} catch (Exception e) {
e.printStackTrace();
}
}
}
synchronized void addClassFile(String className, ClassLoader classLoader) {
if (classLoader == null) {
return;
}
URL resource = classLoader.getResource(className.replace(".", "/") + ".class");
if (resource == null) {
return;
}
Path file = Paths.get(resource.getFile());
if (!Files.exists(file)) {
return;
}
int parentCount = 1;
for (int i = 0; i < className.length(); ++i) {
if (className.charAt(i) == '.' || className.charAt(i) == '/') {
parentCount++;
}
}
try (InputStream in = resource.openStream()) {
hashes.put(file.toAbsolutePath(), MD5.md5(FileReader.readFileBytes(in)));
} catch (IOException e) {
e.printStackTrace();
return;
}
for (int i = 0; i < parentCount; ++i) {
file = file.getParent();
}
if (registered.contains(file)) {
return;
}
registered.add(file);
ClassLoaderData classLoaderData = ClassLoaderData.get(classLoader);
Callback callback = classLoaderData.getAttachment(callbackAttachmentKey);
if (callback == null) {
classLoaderData.putAttachment(callbackAttachmentKey, callback = new Callback(classLoader));
}
watcher.watchPath(file, callback);
}
}