/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2013 Ausenco Engineering Canada Inc.
*
* 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 com.jaamsim.input;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.ErrorException;
import com.jaamsim.units.Unit;
/**
* OutputHandle is a class that represents all the useful runtime information for an output,
* specifically a reference to the runtime annotation and the method it points to
* @author matt.chudleigh
*
*/
public class OutputHandle {
public Entity ent;
public OutputStaticInfo outputInfo;
public Class<? extends Unit> unitType;
private static final HashMap<Class<? extends Entity>, ArrayList<OutputStaticInfo>> outputInfoCache;
static {
outputInfoCache = new HashMap<>();
}
public OutputHandle(Entity e, String outputName) {
ent = e;
outputInfo = OutputHandle.getOutputInfo(e.getClass(), outputName);
unitType = outputInfo.unitType;
}
protected OutputHandle(Entity e) {
ent = e;
}
/**
* A data class containing the 'static' (ie: class derived) information for a single output
*/
private static final class OutputStaticInfo {
public Method method;
public final String name;
public final String desc;
public final boolean reportable;
public final Class<? extends Unit> unitType;
public final int sequence;
public OutputStaticInfo(Method m, Output a) {
method = m;
desc = a.description();
reportable = a.reportable();
name = a.name().intern();
unitType = a.unitType();
sequence = a.sequence();
}
}
// Note: this method will not include attributes in the list. For a complete list use
// Entity.hasOutput()
public static boolean hasOutput(Class<? extends Entity> klass, String outputName) {
return OutputHandle.getOutputInfo(klass, outputName) != null;
}
public static boolean hasOutputInterned(Class<? extends Entity> klass, String outputName) {
return OutputHandle.getOutputInfoInterned(klass, outputName) != null;
}
private static OutputStaticInfo getOutputInfo(Class<? extends Entity> klass, String outputName) {
for (OutputStaticInfo p : getOutputInfoImp(klass)) {
if( p.name.equals(outputName) )
return p;
}
return null;
}
private static OutputStaticInfo getOutputInfoInterned(Class<? extends Entity> klass, String outputName) {
for (OutputStaticInfo p : getOutputInfoImp(klass)) {
if( p.name == outputName )
return p;
}
return null;
}
private static ArrayList<OutputStaticInfo> getOutputInfoImp(Class<? extends Entity> klass) {
ArrayList<OutputStaticInfo> ret = outputInfoCache.get(klass);
if (ret != null)
return ret;
// klass has not been cached yet, generate info
ret = new ArrayList<>();
for (Method m : klass.getMethods()) {
Output a = m.getAnnotation(Output.class);
if (a == null)
continue;
// Check that this method only takes a single double (simTime) parameter
Class<?>[] paramTypes = m.getParameterTypes();
if (paramTypes.length != 1 ||
paramTypes[0] != double.class) {
continue;
}
ret.add(new OutputStaticInfo(m, a));
}
outputInfoCache.put(klass, ret);
return ret;
}
/**
* Return a list of the OuputHandles for the given entity.
* @param e = the entity whose OutputHandles are to be returned.
* @return = ArrayList of OutputHandles.
*/
public static ArrayList<OutputHandle> getOutputHandleList(Entity e) {
Class<? extends Entity> klass = e.getClass();
ArrayList<OutputStaticInfo> list = getOutputInfoImp(klass);
ArrayList<OutputHandle> ret = new ArrayList<>(list.size());
for( OutputStaticInfo p : list ) {
//ret.add( new OutputHandle(e, p) );
ret.add( e.getOutputHandle(p.name) ); // required to get the correct unit type for the output
}
// Add the custom outputs
for (String outputName : e.getCustomOutputNames()) {
ret.add(e.getOutputHandle(outputName));
}
// And the attributes
for (String attribName : e.getAttributeNames()) {
ret.add(e.getOutputHandle(attribName));
}
Collections.sort(ret, new OutputHandleComparator());
return ret;
}
private static class OutputHandleComparator implements Comparator<OutputHandle> {
@Override
public int compare(OutputHandle hand0, OutputHandle hand1) {
Class<?> class0 = hand0.getDeclaringClass();
Class<?> class1 = hand1.getDeclaringClass();
if (class0 == class1) {
if (hand0.getSequence() == hand1.getSequence())
return 0;
else if (hand0.getSequence() < hand1.getSequence())
return -1;
else
return 1;
}
if (class0.isAssignableFrom(class1))
return -1;
else
return 1;
}
}
/**
* Returns true if any of the outputs for the specified class will be printed to the
* output report.
* @param klass - class whose outputs are to be checked.
* @return true if any of the outputs are reportable.
*/
public static boolean isReportable(Class<? extends Entity> klass) {
ArrayList<OutputStaticInfo> list = getOutputInfoImp(klass);
for( OutputStaticInfo p : list ) {
if (p.reportable)
return true;
}
return false;
}
@SuppressWarnings("unchecked") // This suppresses the warning on the cast, which is effectively checked
public <T> T getValue(double simTime, Class<T> klass) {
if( outputInfo.method == null )
return null;
T ret = null;
try {
if (!klass.isAssignableFrom(outputInfo.method.getReturnType()))
return null;
ret = (T)outputInfo.method.invoke(ent, simTime);
}
catch (InvocationTargetException | IllegalAccessException | ClassCastException ex) {
throw new ErrorException(ex);
}
return ret;
}
public boolean canCache() {
return true;
}
public boolean isNumericValue() {
return isNumericType(this.getReturnType());
}
public boolean isIntegerValue() {
return isIntegerType(this.getReturnType());
}
public static boolean isNumericType(Class<?> rtype) {
if (rtype == double.class) return true;
if (rtype == int.class) return true;
if (rtype == long.class) return true;
if (rtype == float.class) return true;
if (rtype == short.class) return true;
if (rtype == char.class) return true;
if (rtype == Double.class) return true;
if (rtype == Integer.class) return true;
if (rtype == Long.class) return true;
if (rtype == Float.class) return true;
if (rtype == Short.class) return true;
if (rtype == Character.class) return true;
return false;
}
public static boolean isIntegerType(Class<?> rtype) {
if (rtype == int.class) return true;
if (rtype == long.class) return true;
if (rtype == Integer.class) return true;
if (rtype == Long.class) return true;
return false;
}
/**
* Checks the output for all possible numerical types and returns a double representing the value
* @param simTime
* @param def - the default value if the return is null or not a number value
* @return
*/
public double getValueAsDouble(double simTime, double def, Unit u) {
double ret = getValueAsDouble(simTime, def);
Class<? extends Unit> ut = this.getUnitType();
if (u == null)
return ret;
if (u.getClass() != ut)
throw new ErrorException("Unit Mismatch");
ret /= u.getConversionFactorToSI();
return ret;
}
/**
* Checks the output for all possible numerical types and returns a double representing the value
* @param simTime
* @param def - the default value if the return is null or not a number value
* @return
*/
public double getValueAsDouble(double simTime, double def) {
Class<?> retType = this.getReturnType();
if (retType == double.class)
return this.getValue(simTime, double.class);
if (retType == int.class)
return this.getValue(simTime, int.class).doubleValue();
if (retType == boolean.class)
return this.getValue(simTime, boolean.class) ? 1.0d : 0.0d;
if (retType == float.class)
return this.getValue(simTime, float.class).doubleValue();
if (retType == long.class)
return this.getValue(simTime, long.class).doubleValue();
if (retType == short.class)
return this.getValue(simTime, short.class).doubleValue();
if (retType == char.class)
return this.getValue(simTime, char.class).charValue();
if (retType == Double.class) {
Double val = getValue(simTime, Double.class);
if (val == null) return def;
return val.doubleValue();
}
if (retType == Integer.class) {
Integer val = getValue(simTime, Integer.class);
if (val == null) return def;
return val.doubleValue();
}
if (retType == Boolean.class) {
Boolean val = getValue(simTime, Boolean.class);
if (val == null) return def;
return val.booleanValue() ? 1.0d : 0.0d;
}
if (retType == Float.class) {
Float val = getValue(simTime, Float.class);
if (val == null) return def;
return val.doubleValue();
}
if (retType == Long.class) {
Long val = getValue(simTime, Long.class);
if (val == null) return def;
return val.doubleValue();
}
if (retType == Short.class) {
Short val = getValue(simTime, Short.class);
if (val == null) return def;
return val.doubleValue();
}
if (retType == Character.class) {
Character val = getValue(simTime, Character.class);
if (val == null) return def;
return val.charValue();
}
return def;
}
public Class<?> getReturnType() {
assert (outputInfo.method != null);
return outputInfo.method.getReturnType();
}
public Class<?> getDeclaringClass() {
assert (outputInfo.method != null);
return outputInfo.method.getDeclaringClass();
}
public void setUnitType(Class<? extends Unit> ut) {
unitType = ut;
}
public Class<? extends Unit> getUnitType() {
return unitType;
}
public String getDescription() {
return outputInfo.desc;
}
public String getName() {
return outputInfo.name;
}
public boolean isReportable() {
return outputInfo.reportable;
}
public int getSequence() {
return outputInfo.sequence;
}
// Lookup an outputs return type from the class and output name only
public static Class<?> getStaticOutputType(Class<?> klass, String outputName) {
if (!Entity.class.isAssignableFrom(klass)) {
return null;
}
@SuppressWarnings("unchecked")
ArrayList<OutputStaticInfo> infos = getOutputInfoImp((Class<? extends Entity>)klass);
for (OutputStaticInfo info : infos) {
if (info.name.equals(outputName)) {
return info.method.getReturnType();
}
}
return null;
}
// Lookup an outputs return type from the unit type
public static Class<? extends Unit> getStaticOutputUnitType(Class<?> klass, String outputName) {
if (!Entity.class.isAssignableFrom(klass)) {
return null;
}
@SuppressWarnings("unchecked")
ArrayList<OutputStaticInfo> infos = getOutputInfoImp((Class<? extends Entity>)klass);
for (OutputStaticInfo info : infos) {
if (info.name.equals(outputName)) {
return info.unitType;
}
}
return null;
}
}