Skip to content

Commit

Permalink
Changed singleton component instantiation (issue #2)
Browse files Browse the repository at this point in the history
  • Loading branch information
MTrop committed Aug 3, 2020
1 parent 8a27f4c commit 81beef5
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 111 deletions.
6 changes: 6 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ Small (C) Black Rook Software 2020
by Matt Tropiano et al. (see AUTHORS.txt)


Changed in 1.5.2
----------------

- `Fixed` Duplicate singleton component issue. [Issue #2](https://github.com/BlackRookSoftware/Small/issues/2).


Changed in 1.5.1
----------------

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/blackrook/small/SmallComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,10 @@ private boolean isValidBeforeDestructionMethod(Method method)
;
}

@Override
public String toString()
{
return getClass().getSimpleName() + ": " + instance.getClass().toString();
}

}
236 changes: 125 additions & 111 deletions src/main/java/com/blackrook/small/SmallEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.regex.PatternSyntaxException;
Expand Down Expand Up @@ -90,15 +91,14 @@ public class SmallEnvironment implements HttpSessionAttributeListener, HttpSessi
/** The method to path to controller trie. */
private Map<RequestMethod, URITrie<ControllerEntryPoint>> controllerEntries;

/** The components that are instantiated, class set. */
private Set<Class<?>> componentSet;
/** The components that are instantiated. */
private List<SmallComponent> componentList;
/** The components that are instantiated mapped by type. */
/** The components that are instantiated mapped by type (super-types and specific). */
private HashDequeMap<Class<?>, SmallComponent> componentTypeMapping;
/** The controllers that were instantiated. */

/** All components that were instantiated (mapped by specific class type). */
private Map<Class<?>, SmallComponent> allComponents;
/** The controllers that were instantiated (mapped by specific class type). */
private Map<Class<?>, ControllerComponent> controllerComponents;
/** The filters that were instantiated. */
/** The filters that were instantiated (mapped by specific class type). */
private Map<Class<?>, FilterComponent> filterComponents;

/** List of components that listen for session events. */
Expand All @@ -123,10 +123,9 @@ public class SmallEnvironment implements HttpSessionAttributeListener, HttpSessi

this.componentsConstructing = new HashSet<>();

this.componentSet = new HashSet<>();
this.componentList = new LinkedList<>();
this.componentTypeMapping = new HashDequeMap<>();
this.controllerEntries = new HashMap<>(8);
this.allComponents = new HashMap<>(32);
this.controllerComponents = new HashMap<>(16);
this.filterComponents = new HashMap<>(16);

Expand All @@ -148,14 +147,17 @@ void init(ServletContext context, String[] controllerRootPackages, File tempDir)
this.xmlDriver = null;
this.mimeTypeDriver = DEFAULT_MIME;

registerComponent(new SmallComponent(context));
registerComponent(new SmallComponent(SmallUtils.getConfiguration(context)));
registerComponent(new SmallComponent(this));
// Pre-register some application-specific objects.
registerComponent(context.getClass(), new SmallComponent(context));
registerComponent(getClass(), new SmallComponent(this));
SmallComponent appConfigComponent = new SmallComponent(SmallUtils.getConfiguration(context));
registerComponent(appConfigComponent);
allComponents.put(SmallConfiguration.class, appConfigComponent);

if (!Utils.isEmpty(controllerRootPackages))
initComponents(context, controllerRootPackages);
for (SmallComponent sc : componentList)
sc.invokeAfterInitializeMethods(this);
for (Entry<Class<?>, SmallComponent> sc : allComponents.entrySet())
sc.getValue().invokeAfterInitializeMethods(this);
}

/**
Expand All @@ -164,10 +166,10 @@ void init(ServletContext context, String[] controllerRootPackages, File tempDir)
void destroy(ServletContext context)
{
// Destroy all components.
for (SmallComponent sc : componentList)
for (Entry<Class<?>, SmallComponent> sc : allComponents.entrySet())
{
try {
sc.invokeBeforeDestructionMethods();
sc.getValue().invokeBeforeDestructionMethods();
} catch (Exception e) {
context.log("Exception on destroy: ", e);
}
Expand All @@ -181,8 +183,8 @@ void destroy(ServletContext context)
exceptionHandlerMap.clear();
componentsConstructing.clear();
controllerEntries.clear();
componentList.clear();
componentTypeMapping.clear();
allComponents.clear();
controllerComponents.clear();
filterComponents.clear();
contextListeners.clear();
Expand Down Expand Up @@ -239,82 +241,10 @@ private void initComponents(ServletContext context, String[] packageNames)
if (componentClass.isAnnotationPresent(Component.class))
{
// check for double-include. Skip.
if (componentSet.contains(componentClass))
if (allComponents.containsKey(componentClass))
continue;

Object componentInstance = createComponent(componentClass);

if (ServletContextListener.class.isAssignableFrom(componentClass))
contextListeners.add((ServletContextListener)componentInstance);

if (HttpSessionListener.class.isAssignableFrom(componentClass))
sessionListeners.add((HttpSessionListener)componentInstance);

if (HttpSessionAttributeListener.class.isAssignableFrom(componentClass))
sessionAttributeListeners.add((HttpSessionAttributeListener)componentInstance);

if (JSONDriver.class.isAssignableFrom(componentClass))
jsonDriver = (JSONDriver)componentInstance;

if (XMLDriver.class.isAssignableFrom(componentClass))
xmlDriver = (XMLDriver)componentInstance;

if (MIMETypeDriver.class.isAssignableFrom(componentClass))
mimeTypeDriver = (MIMETypeDriver)componentInstance;

if (ViewDriver.class.isAssignableFrom(componentClass))
viewDriverList.add((ViewDriver)componentInstance);

if (ExceptionHandler.class.isAssignableFrom(componentClass))
exceptionHandlerMap.put(((ExceptionHandler<?>)componentInstance).getHandledClass(), componentInstance);

SmallComponent component;
if (componentClass.isAnnotationPresent(Controller.class))
{
if (componentClass.isAnnotationPresent(Filter.class))
throw new SmallFrameworkSetupException("Class " + componentClass+ " is already a Controller. Can't annotate with @Filter!");

component = new ControllerComponent(componentInstance);
controllerComponents.put(componentClass, (ControllerComponent)component);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();

EntryPath entryPathAnno = componentClass.getAnnotation(EntryPath.class);

String path = SmallUtils.trimSlashes(entryPathAnno != null ? entryPathAnno.value() + '/' : "");
for (ControllerEntryPoint entryPoint : ((ControllerComponent)component).getEntryMethods())
{
String uri = path + '/' + SmallUtils.trimSlashes(entryPoint.getPath());
for (RequestMethod rm : entryPoint.getRequestMethods())
{
URITrie<ControllerEntryPoint> trie;
if ((trie = controllerEntries.get(rm)) == null)
controllerEntries.put(rm, trie = new URITrie<>());

try {
trie.add(uri, entryPoint);
} catch (PatternSyntaxException e) {
throw new SmallFrameworkSetupException("Could not set up controller "+componentClass+", method "+entryPoint.getMethod(), e);
}
}
}
}
else if (componentClass.isAnnotationPresent(Filter.class))
{
component = new FilterComponent(componentInstance);
filterComponents.put(componentClass, (FilterComponent)component);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();
}
else
{
component = new SmallComponent(componentInstance);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();
}
createSmallComponent(componentClass);
}
else if (allowWebSockets)
{
Expand Down Expand Up @@ -356,27 +286,12 @@ else if (Endpoint.class.isAssignableFrom(componentClass))
}
}

/**
* Creates or gets an engine singleton component by class.
* @param clazz the class to create/retrieve.
*/
private <T> T createOrGetComponent(Class<T> clazz)
{
T out;
if ((out = getComponent(clazz)) != null)
return (T)out;
else
{
return createComponent(clazz);
}
}

/**
* Creates a new component for a class and using one of its constructors.
* @param clazz the class to instantiate.
* @return the new class instance.
*/
private <T> T createComponent(Class<T> clazz)
private <T> T createInstance(Class<T> clazz)
{
T object = null;

Expand All @@ -403,8 +318,8 @@ private <T> T createComponent(Class<T> clazz)
else
{
if (ServletContext.class.isAssignableFrom(types[i])) // should already exist
createOrGetComponent(types[i]);
params[i] = createOrGetComponent(types[i]);
createOrGetSmallComponent(types[i]);
params[i] = createOrGetSmallComponent(types[i]).getInstance();
}
}

Expand All @@ -414,11 +329,110 @@ private <T> T createComponent(Class<T> clazz)
}
}

private <T> SmallComponent createSmallComponent(Class<T> componentClass)
{
Object componentInstance = createInstance(componentClass);

if (ServletContextListener.class.isAssignableFrom(componentClass))
contextListeners.add((ServletContextListener)componentInstance);

if (HttpSessionListener.class.isAssignableFrom(componentClass))
sessionListeners.add((HttpSessionListener)componentInstance);

if (HttpSessionAttributeListener.class.isAssignableFrom(componentClass))
sessionAttributeListeners.add((HttpSessionAttributeListener)componentInstance);

if (JSONDriver.class.isAssignableFrom(componentClass))
jsonDriver = (JSONDriver)componentInstance;

if (XMLDriver.class.isAssignableFrom(componentClass))
xmlDriver = (XMLDriver)componentInstance;

if (MIMETypeDriver.class.isAssignableFrom(componentClass))
mimeTypeDriver = (MIMETypeDriver)componentInstance;

if (ViewDriver.class.isAssignableFrom(componentClass))
viewDriverList.add((ViewDriver)componentInstance);

if (ExceptionHandler.class.isAssignableFrom(componentClass))
exceptionHandlerMap.put(((ExceptionHandler<?>)componentInstance).getHandledClass(), componentInstance);

SmallComponent component;
if (componentClass.isAnnotationPresent(Controller.class))
{
if (componentClass.isAnnotationPresent(Filter.class))
throw new SmallFrameworkSetupException("Class " + componentClass+ " is already a Controller. Can't annotate with @Filter!");

component = new ControllerComponent(componentInstance);
controllerComponents.put(componentClass, (ControllerComponent)component);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();

EntryPath entryPathAnno = componentClass.getAnnotation(EntryPath.class);

String path = SmallUtils.trimSlashes(entryPathAnno != null ? entryPathAnno.value() + '/' : "");
for (ControllerEntryPoint entryPoint : ((ControllerComponent)component).getEntryMethods())
{
String uri = path + '/' + SmallUtils.trimSlashes(entryPoint.getPath());
for (RequestMethod rm : entryPoint.getRequestMethods())
{
URITrie<ControllerEntryPoint> trie;
if ((trie = controllerEntries.get(rm)) == null)
controllerEntries.put(rm, trie = new URITrie<>());

try {
trie.add(uri, entryPoint);
} catch (PatternSyntaxException e) {
throw new SmallFrameworkSetupException("Could not set up controller "+componentClass+", method "+entryPoint.getMethod(), e);
}
}
}
}
else if (componentClass.isAnnotationPresent(Filter.class))
{
component = new FilterComponent(componentInstance);
filterComponents.put(componentClass, (FilterComponent)component);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();
}
else
{
component = new SmallComponent(componentInstance);
registerComponent(component);
component.scanMethods();
component.invokeAfterConstructionMethods();
}
return component;
}

/**
* Creates or gets an engine singleton component by class.
* @param clazz the class to create/retrieve.
*/
private <T> SmallComponent createOrGetSmallComponent(Class<T> clazz)
{
SmallComponent out;
if ((out = allComponents.get(clazz)) != null)
return out;
else
{
return createSmallComponent(clazz);
}
}

// Register under a component's class mapping.
private void registerComponent(SmallComponent component)
{
componentList.add(component);
registerComponent(component.getInstance().getClass(), component);
}

// Register under a specific class mapping.
private void registerComponent(Class<?> clazz, SmallComponent component)
{
Class<?> componentClass = component.getInstance().getClass();
componentSet.add(componentClass);
allComponents.put(componentClass, component);
registerComponentTree(componentClass, component);
}

Expand Down

0 comments on commit 81beef5

Please sign in to comment.