Test coverage report for ProcedureCache.java - www.sdmetrics.com
/*
* SDMetrics Open Core for UML design measurement
* Copyright (c) Juergen Wuest
* To contact the author, see <http://www.sdmetrics.com/Contact.html>.
*
* This file is part of the SDMetrics Open Core.
*
* SDMetrics Open Core is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
* SDMetrics Open Core 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SDMetrics Open Core. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.sdmetrics.metrics;
import java.util.HashMap;
import java.util.LinkedList;
/**
* A cache for calculation procedure and expression operation instances during a
* calculation run.
* <p>
* Each calculation of a metric, set, or rule for a model elements requires a
* calculation procedure instance that executes the calculation algorithm.
* Likewise, each operation in a metric, set or boolean expression requires an
* operation instance to calculate the operation value. Because the calculation
* classes are configurable, their instantiation involves reflection. To avoid
* having to create lots of calculation procedure instances by reflection, we
* keep them in a cache for reuse.
*
* @param <T> The type of calculation procedures stored in this cache.
*/
abstract class ProcedureCache<T extends AbstractProcedure> {
/** Represents one calculation procedure class. */
private class ProcedureInfo {
/** The calculation procedure class. */
Class<? extends T> procedureClass;
/** The available instances of the class. */
LinkedList<T> availableInstances = new LinkedList<>();
}
/**
* Stores the calculation procedure instances by their procedure names
* (projection, compoundmetric, etc).
*/
private final HashMap<String, ProcedureInfo> buckets = new HashMap<>();
/**
* Describes elements in the cache (such as"metric procedure" or
* "boolean operator").
*/
private final String procedureDescription;
/**
* Constructor.
*
* @param description Description of the elements in the cache.
*/
ProcedureCache(String description) {
this.procedureDescription = description;
}
/**
* Adds a custom defined procedure from the metric definition file. Checks
* if the procedure class can be loaded, has the expected type, and can be
* instantiated.
*
* @param name Name of the procedure
* @param className Fully qualified class name of the procedure
* @throws SDMetricsException Procedure class is unsuitable.
*/
void addProcedureClass(String name, String className)
throws SDMetricsException {
Class<? extends T> procedureClass = null;
try {
procedureClass = loadClass(className);
} catch (Exception ex) {
throw new SDMetricsException(
null,
null,
"Could not load class '"
+ className
+ "'. "
+ "\n"
+ ex.getClass().getSimpleName()
+ ": "
+ ex.getMessage()
+ "\nMake sure the class is on the classpath, has public visibility, and extends the required base class.");
}
addProcedureClass(name, procedureClass);
// We create the first instance immediately to find potential errors
// as early as possible.
T procedure = getProcedure(name);
returnProcedure(procedure);
}
/**
* Adds a standard procedure that is available at compile time.
*
* @param name Name of the procedure
* @param procClass Class of the procedure
*/
void addProcedureClass(String name, Class<? extends T> procClass) {
ProcedureInfo procInfo = new ProcedureInfo();
procInfo.procedureClass = procClass;
buckets.put(name, procInfo);
}
/**
* Checks if this cache has a calculation procedure of a given name.
*
* @param name Name of the procedure
* @return <code>true</code> if a procedure class has been registered for
* this name
*/
boolean hasProcedure(String name) {
return buckets.containsKey(name);
}
/**
* Obtains a procedure instance from the cache.
* <p>
* The instance should be returned to the cache after use.
*
* @param procedureName Name of the procedure
* @return An instance the procedure's class
* @throws SDMetricsException The instance could not be created.
*/
T getProcedure(String procedureName) throws SDMetricsException {
ProcedureInfo procInfo = buckets.get(procedureName);
if (procInfo == null) {
throw new SDMetricsException(null, null, "Unknown "
+ procedureDescription + " '" + procedureName + "'.");
}
if (procInfo.availableInstances.isEmpty()) {
try {
// Create and return a new instance
T result = procInfo.procedureClass.getConstructor(new Class<?>[0])
.newInstance(new Object[0]);
result.setName(procedureName);
return result;
} catch (Exception ex) {
throw new SDMetricsException(
null,
null,
"Could not instantiate class '"
+ procInfo.procedureClass.getName()
+ "'. "
+ "\n"
+ ex.getClass().getSimpleName()
+ ": "
+ ex.getMessage()
+ "\nMake sure the class has a public constructor with empty argument list and throws no exceptions.");
}
}
// return an existing instance from the cache
return procInfo.availableInstances.removeLast();
}
/**
* Returns a procedure instance to the cache for reuse.
*
* @param procedure The procedure instance.
*/
void returnProcedure(T procedure) {
procedure.clear();
ProcedureInfo procInfo = buckets.get(procedure.getName());
procInfo.availableInstances.add(procedure);
}
/**
* Load the procedure class.
*
* @param className Fully qualified class name of the procedure class.
* @return Class object for the specified class
* @throws ClassNotFoundException The class could not be loaded.
*/
protected abstract Class<? extends T> loadClass(String className)
throws ClassNotFoundException;
}