ClassfileLoaderEventSource.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.classreader;
import org.apache.logging.log4j.*;
import java.io.*;
import java.util.*;
public abstract class ClassfileLoaderEventSource extends ClassfileLoader {
public static final ClassfileLoaderDispatcher DEFAULT_DISPATCHER = new PermissiveDispatcher();
private final ClassfileFactory factory;
private final ClassfileLoaderDispatcher dispatcher;
private final ClassfileLoader dirLoader = new DirectoryClassfileLoader(this);
private final ClassfileLoader jarLoader;
private final ClassfileLoader zipLoader = new ZipClassfileLoader(this);
private final Collection<LoadListener> loadListeners = new HashSet<>();
private final LinkedList<String> groupNames = new LinkedList<>();
private final LinkedList<Integer> groupSizes = new LinkedList<>();
private ClassfileLoaderAction previousDispatch;
public ClassfileLoaderEventSource(ClassfileFactory factory) {
this.factory = factory;
this.dispatcher = DEFAULT_DISPATCHER;
jarLoader = new JarClassfileLoader(this);
}
public ClassfileLoaderEventSource(ClassfileFactory factory, int targetJdk) {
this.factory = factory;
this.dispatcher = DEFAULT_DISPATCHER;
jarLoader = new JarClassfileLoader(this, targetJdk);
}
public ClassfileLoaderEventSource(ClassfileFactory factory, ClassfileLoaderDispatcher dispatcher) {
this.factory = factory;
this.dispatcher = dispatcher;
jarLoader = new JarClassfileLoader(this);
}
public ClassfileLoaderEventSource(ClassfileFactory factory, int targetJdk, ClassfileLoaderDispatcher dispatcher) {
this.factory = factory;
this.dispatcher = dispatcher;
jarLoader = new JarClassfileLoader(this, targetJdk);
}
protected ClassfileFactory getFactory() {
return factory;
}
protected void load(String filename) {
ClassfileLoaderAction dispatch = dispatcher.dispatch(filename);
previousDispatch = dispatch;
switch (dispatch) {
case IGNORE -> LogManager.getLogger(getClass()).debug("IGNORE \"{}\"", filename);
case CLASS, DIRECTORY -> {
LogManager.getLogger(getClass()).debug("DIRECTORY or CLASS \"{}\"", filename);
dirLoader.load(filename);
}
case ZIP -> {
LogManager.getLogger(getClass()).debug("ZIP \"{}\"", filename);
zipLoader.load(filename);
}
case JAR -> {
LogManager.getLogger(getClass()).debug("JAR \"{}\"", filename);
jarLoader.load(filename);
}
default -> LogManager.getLogger(getClass()).debug("default (IGNORE) \"{}\"", filename);
}
}
protected void load(String filename, InputStream in) {
ClassfileLoaderAction dispatch = dispatcher.dispatch(filename);
if (dispatch == ClassfileLoaderAction.IGNORE && getTopGroupSize() == 1 && filename.equals(getTopGroupName())) {
dispatch = previousDispatch;
}
switch (dispatch) {
case IGNORE -> LogManager.getLogger(getClass()).debug("IGNORE \"{}\"", filename);
case DIRECTORY -> {
LogManager.getLogger(getClass()).debug("DIRECTORY \"{}\"", filename);
dirLoader.load(filename, in);
}
case ZIP -> {
LogManager.getLogger(getClass()).debug("ZIP \"{}\"", filename);
zipLoader.load(filename, in);
}
case JAR -> {
LogManager.getLogger(getClass()).debug("JAR \"{}\"", filename);
jarLoader.load(filename, in);
}
case CLASS -> {
LogManager.getLogger(getClass()).debug("CLASS \"{}\"", filename);
try {
fireBeginClassfile(filename);
Classfile classfile = load(new DataInputStream(in));
fireEndClassfile(filename, classfile);
} catch (Exception ex) {
LogManager.getLogger(getClass()).warn("Cannot load class from file \"{}\"", filename, ex);
}
}
default -> LogManager.getLogger(getClass()).debug("default (IGNORE) \"{}\"", filename);
}
}
private String getTopGroupName() {
return groupNames.isEmpty() ? null : groupNames.getLast();
}
private void pushGroupName(String groupName) {
groupNames.addLast(groupName);
}
private void popGroupName() {
groupNames.removeLast();
}
private int getTopGroupSize() {
return groupSizes.isEmpty() ? 0 : groupSizes.getLast();
}
private void pushGroupSize(int size) {
groupSizes.addLast(size);
}
private void popGroupSize() {
groupSizes.removeLast();
}
public void addLoadListener(LoadListener listener) {
loadListeners.add(listener);
}
public void removeLoadListener(LoadListener listener) {
loadListeners.remove(listener);
}
protected void fireBeginSession() {
LogManager.getLogger(getClass()).debug("Begin session");
var event = new LoadEvent(this, null, null, null);
loadListeners.forEach(listener -> listener.beginSession(event));
}
protected void fireBeginGroup(String groupName, int size) {
LogManager.getLogger(getClass()).debug("Begin group \"{}\" of size {}", groupName, size);
LoadEvent event = new LoadEvent(this, groupName, size);
loadListeners.forEach(listener -> listener.beginGroup(event));
pushGroupName(groupName);
pushGroupSize(size);
}
protected void fireBeginFile(String filename) {
LogManager.getLogger(getClass()).debug("Begin file \"{}\"", filename);
LoadEvent event = new LoadEvent(this, getTopGroupName(), filename, null);
loadListeners.forEach(listener -> listener.beginFile(event));
}
protected void fireBeginClassfile(String filename) {
LogManager.getLogger(getClass()).debug("Begin classfile \"{}\"", filename);
LoadEvent event = new LoadEvent(this, getTopGroupName(), filename, null);
loadListeners.forEach(listener -> listener.beginClassfile(event));
}
protected void fireEndClassfile(String filename, Classfile classfile) {
LogManager.getLogger(getClass()).debug("End classfile \"{}\": {}", () -> filename, () -> ((classfile != null) ? classfile.getClassName() : "nothing"));
LoadEvent event = new LoadEvent(this, getTopGroupName(), filename, classfile);
loadListeners.forEach(listener -> listener.endClassfile(event));
}
protected void fireEndFile(String filename) {
LogManager.getLogger(getClass()).debug("End file \"{}\"", filename);
LoadEvent event = new LoadEvent(this, getTopGroupName(), filename, null);
loadListeners.forEach(listener -> listener.endFile(event));
}
protected void fireEndGroup(String groupName) {
LogManager.getLogger(getClass()).debug("End group \"{}\"", groupName);
LoadEvent event = new LoadEvent(this, groupName, null, null);
loadListeners.forEach(listener -> listener.endGroup(event));
popGroupName();
popGroupSize();
}
protected void fireEndSession() {
LogManager.getLogger(getClass()).debug("End session");
LoadEvent event = new LoadEvent(this, null, null, null);
loadListeners.forEach(listener -> listener.endSession(event));
}
}