Преглед изворни кода

new: CollectionUtils:map

new: RuntimeUtils#getMemoryUse
new: RuntimeUtils#performGC
new: RuntimeUtils#forceGC
new: RuntimeUtils#invokedInsideMaven
new: RuntimeUtils#invokedInsideSingleTest

new: UnitFormat

new: LoggerUtils#getLogger
new: LoggerUtils#getTraceLogger
new: LoggerUtils#

new: StackInspector
Ranides Atterwim пре 10 година
родитељ
комит
427205243f

+ 11 - 0
assira/pom.xml

@@ -44,5 +44,16 @@
             <version>2.0.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.12</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.13</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>

+ 30 - 0
assira/src/main/java/net/ranides/assira/collection/CollectionUtils.java

@@ -0,0 +1,30 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.collection;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class CollectionUtils {
+    
+    private CollectionUtils() { }
+    
+    public static <S,T> List<T> map(S[] array, Function<S, T> function) {
+        return Arrays.stream(array).map(function).collect(Collectors.toList());
+    }
+    
+    public static <S,T> List<T> map(Collection<S> list, Function<S, T> function) {
+        return list.stream().map(function).collect(Collectors.toList());
+    }
+}

+ 1 - 1
assira/src/main/java/net/ranides/assira/collection/arrays/ArrayUtils.java

@@ -208,7 +208,7 @@ public final class ArrayUtils {
     public static int size(Object array) {
         return null == array ? 0 : Array.getLength(array);
     }
-    
+  
     @SuppressWarnings("PMD.NPathComplexity")
     public static List<?> wrap(Object array) {
         Class<?> c = array.getClass().getComponentType();

+ 1 - 1
assira/src/main/java/net/ranides/assira/collection/lists/IntArrayList.java

@@ -209,7 +209,7 @@ public class IntArrayList extends AIntList implements BlockCollection<Integer>,
      * without resizing.
      *
      * @param capacity the new minimum capacity for this array list.
-     * @todo (assira # 2) move #ensureCapacity to BlockCollection
+     * @todo (assira # 5) move #ensureCapacity to BlockCollection
      */
     public void ensureCapacity(int capacity) {
         data = IntArrayAllocator.ensureCapacity(data, capacity, size);

+ 92 - 0
assira/src/main/java/net/ranides/assira/system/RuntimeUtils.java

@@ -0,0 +1,92 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.system;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.LinkedList;
+import java.util.List;
+import net.ranides.assira.text.UnitFormat;
+import net.ranides.assira.trace.LoggerUtils;
+import net.ranides.assira.trace.StackInspector;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class RuntimeUtils {
+    
+    private static final org.slf4j.Logger LOGGER = LoggerUtils.getLogger();
+    
+    private RuntimeUtils() {
+        // utility class
+    }
+    
+    public static long getMemoryUse() {
+        Runtime runtime = Runtime.getRuntime();
+        return runtime.totalMemory() - runtime.freeMemory();
+    }
+    
+    
+    /**
+     * Runs the garbage collector. 
+     * @return 
+     *      Estimated amount of memory released by GC
+     *      
+     */
+    @SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly")
+    public static long performGC() {
+        long prev = getMemoryUse();
+        Runtime.getRuntime().gc();
+        return prev - getMemoryUse();
+    }
+    
+    /**
+     * Forces releasing {@code SoftReferences}, by causing OutOfMemoryError in VM.
+     * @return 
+     *      Estimated amount of memory released by GC
+     */
+    @SuppressWarnings({
+        "PMD.DoNotCallGarbageCollectionExplicitly",
+        "MismatchedQueryAndUpdateOfCollection"
+    })
+    public static long forceGC() {
+        long prev = Runtime.getRuntime().totalMemory();
+        int block = (int)Math.min(Integer.MAX_VALUE, Runtime.getRuntime().freeMemory());
+        try {
+            final List<long[]> memhog = new LinkedList<>();
+            while(true) {
+                memhog.add(new long[block]);
+            }
+        }
+        catch(final OutOfMemoryError e) {
+            // at this point all SoftReferences have been released - GUARANTEED
+            long alloc = Runtime.getRuntime().totalMemory() - prev;
+            LOGGER.debug("Forced OutOfMemoryError by allocation {} bytes", UnitFormat.format(alloc));
+            Runtime.getRuntime().gc();
+            // at this point we should use minimal amount of memory
+            return prev - getMemoryUse();
+        }
+    }
+    
+    
+    public static boolean invokedInsideMaven() {
+        return StackInspector.getCallerNames()
+            .stream().anyMatch((c)->c.startsWith("org.apache.maven.surefire."));
+    }
+
+    public static boolean invokedInsideSingleTest() {
+        return Li.SINGLE_TEST;
+    }
+    
+    private static final class Li { // NOPMD lazy init idiom
+        
+        private static final boolean SINGLE_TEST = null != AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("test"));
+        
+    }
+
+}

+ 119 - 0
assira/src/main/java/net/ranides/assira/text/UnitFormat.java

@@ -0,0 +1,119 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.text;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.ranides.assira.collection.maps.HashMap;
+
+/**
+ *
+ * @author ranides
+ */
+public final class UnitFormat {
+
+    @java.lang.SuppressWarnings("unchecked")
+    private static final Map<String, Long> PREFIX = new HashMap<>(
+        new String[]{"K", "M", "G", "T", "k", "m", "g", "t"}, 
+        new Long[]{
+            1024L, 1024L * 1024L, 1024L * 1024L * 1024L, 1024L * 1024L * 1024L * 1024L,
+            1000L, 1000L * 1000L, 1000L * 1000L * 1000L, 1000L * 1000L * 1000L * 1000L
+    });
+
+    private static final Pattern PATTERN_ENG = Pattern.compile("([0-9\\. ]+)([KMGTkmgt])?([Bb])?");
+
+    private UnitFormat() { /* utility class */ }
+
+    public static long asLong(String value, long ddefault) {
+        try {
+            return asLong(value);
+        } catch(NumberFormatException _e) {
+            return ddefault;
+        }
+    }
+
+    public static int asInt(String value, int ddefault) {
+        try {
+            return asInt(value);
+        } catch(NumberFormatException _e) {
+            return ddefault;
+        }
+    }
+
+    public static double asDouble(String value, double ddefault) {
+        try {
+            return asDouble(value);
+        } catch(NumberFormatException _e) {
+            return ddefault;
+        }
+    }
+
+    public static long asLong(String value) {
+        Matcher hit = PATTERN_ENG.matcher(value);
+        if(hit.matches()) {
+            return asLong(hit);
+        } else {
+            return Long.parseLong( value.trim() );
+        }
+    }
+
+    public static int asInt(String value) {
+        Matcher hit = PATTERN_ENG.matcher(value);
+        if(hit.matches()) {
+            return (int)asLong(hit);
+        } else {
+            return Integer.parseInt( value.trim() );
+        }
+    }
+
+    public static double asDouble(String value) {
+        Matcher hit = PATTERN_ENG.matcher(value);
+        if(hit.matches()) {
+            return asDouble(hit);
+        } else {
+            return Double.parseDouble( value.trim() );
+        }
+    }
+
+    private static double asDouble(Matcher hit) {
+        // @todo (assira # 4) StringUtils: remove(text,char)
+        double value = Double.parseDouble(hit.group(1).replaceAll(" ", ""));
+        if(null !=hit.group(2)) {
+            value *= PREFIX.get(hit.group(2));
+        }
+        if("b".equals(hit.group(3))) {
+            value /= 8;
+        }
+        return value;
+    }
+
+    private static long asLong(Matcher hit) {
+        // @todo (assira # 4) StringUtils: remove(text,char)
+        long value = Long.parseLong(hit.group(1).replaceAll(" ", ""));
+        if(null !=hit.group(2)) {
+            value *= PREFIX.get(hit.group(2));
+        }
+        if("b".equals(hit.group(3))) {
+            value /= 8;
+        }
+        return value;
+    }
+
+    public static String format(long value) {
+        StringBuilder ret = new StringBuilder();
+        String text = Long.toString(value, 10);
+        for(int i=0, n=text.length()-1; i<=n; i++) {
+            ret.append(text.charAt(i));
+            if( n>i && ((n-i)%3 == 0) ) {
+                ret.append('\'');
+}
+        }
+        return ret.toString();
+    }
+
+}

+ 6 - 16
assira/src/main/java/net/ranides/assira/trace/ExceptionInspector.java

@@ -11,14 +11,14 @@ import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.function.Function;
-import java.util.stream.Collectors;
+import net.ranides.assira.collection.CollectionUtils;
 
 /**
  *
  * @author ranides
+ * @todo (assira # 1) totalnie to przebudować, bo 3/4 metod jest bez sensu.
  */
 public final class ExceptionInspector {
 
@@ -49,7 +49,7 @@ public final class ExceptionInspector {
     }
 
     public static List<String> getFilesList(Throwable cause) {
-        return map(cause.getStackTrace(), (item) -> String.format("%4d : %s", item.getLineNumber(), item.getFileName()));
+        return CollectionUtils.map(cause.getStackTrace(), (item) -> String.format("%4d : %s", item.getLineNumber(), item.getFileName()));
     }
     
     public static String getStackTrace(Throwable cause) {
@@ -76,8 +76,8 @@ public final class ExceptionInspector {
 
     }
     
-    public static AssertionError assertError(String message, Throwable cause) {
-        AssertionError error = new AssertionError(message);
+    public static AssertionError assertError(Throwable cause, String message, Object args) {
+        AssertionError error = new AssertionError(String.format(message, args));
         error.initCause(cause);
         return error;
     }
@@ -114,18 +114,8 @@ public final class ExceptionInspector {
 		}
     }
     
-    // @todo (assira # 4) move to collection.lists
-
     private static <T> List<T> map(Throwable cause, Function<Throwable, T> function) {
-        return getCauseList(cause).stream().map(function).collect(Collectors.toList());
-    }
-    
-    private static <S,T> List<T> map(S[] list, Function<S, T> function) {
-        return Arrays.asList(list).stream().map(function).collect(Collectors.toList());
+        return CollectionUtils.map(getCauseList(cause), function);
     }
     
-    private static <S,T> List<T> map(List<S> list, Function<S, T> function) {
-        return list.stream().map(function).collect(Collectors.toList());
-    }
-
 }

+ 81 - 0
assira/src/main/java/net/ranides/assira/trace/LoggerUtils.java

@@ -0,0 +1,81 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.trace;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class LoggerUtils {
+    
+    private LoggerUtils() { }
+    
+    /**
+     * Returns logger for the "current" class, so you don't have to hardcode class name.
+     * @return 
+     */
+    public static org.slf4j.Logger getLogger() {
+        return LoggerFactory.getLogger(Thread.currentThread().getStackTrace()[2].getClassName());
+    }
+    
+    /**
+     * Returns logger printing messages with any logging level.
+     * <p><b>Warning:</b> It works only with manually invoked JUnit tests configured
+     * to use {@code slf4j-simple}. In other environments it works exactly in the 
+     * same way as {@link #getLogger()}. </p>
+     * @return 
+     */
+    public static org.slf4j.Logger getTraceLogger() {
+        if( isInsideSingleTest() ) {
+            initTraceLogger();
+        }
+        return LoggerFactory.getLogger(Thread.currentThread().getStackTrace()[2].getClassName());
+    }
+
+    private static boolean isInsideSingleTest() {
+        // we don't use RuntimeUtils because we must avoid slf4j initialization
+        return null != AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("test"));
+    }
+
+    private static void initTraceLogger() throws AssertionError {
+        if( isLoaded("org.slf4j.impl.SimpleLogger") ) {
+            throw new AssertionError("org.slf4j.SimpleLogger is already loaded. You can't reconfigure it now.");
+        }
+        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace");
+    }
+    
+    // @todo (assira # 3) reflection: ClassInspector#isLoaded
+    // @todo (assira # 3) reflection: GenericMethod
+    
+    private static boolean isLoaded(String name) {
+        try {
+            return null != Li.FIND_LOADED_CLASS.invoke(Thread.currentThread().getContextClassLoader(), name);
+        } catch(ReflectiveOperationException roe) {
+            throw ExceptionInspector.rethrow(roe);
+        }
+    }
+    
+    private static final class Li { // NOPMD - lazy init idiom
+        
+        public static final java.lang.reflect.Method FIND_LOADED_CLASS;
+        
+        static {
+            try {
+                FIND_LOADED_CLASS = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[] { String.class });
+                FIND_LOADED_CLASS.setAccessible(true);
+            } catch(ReflectiveOperationException ex) {
+                throw ExceptionInspector.rethrow(ex);
+            }
+        }
+        
+    }
+
+}

+ 276 - 0
assira/src/main/java/net/ranides/assira/trace/StackInspector.java

@@ -0,0 +1,276 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.trace;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+import net.ranides.assira.generic.ValueUtils;
+
+/**
+ * 
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class StackInspector {
+    
+    // Please note that class is implemented in very verbose way:
+    // we don't use any functional interfaces, adapters, JDK Stream API etc.
+    // 
+    // Every syntactic elegance will slow down us. It matters, because functions 
+    // are designed to be *extremaly* fast (~1 us per call) and every overhead
+    // is noticeable.
+    //
+    // We use "package scope" because we want to test our resolvers in junit
+
+    
+    static final Resolver RESOLVER_SR = SunResolver.get();
+    
+    static final Resolver RESOLVER_SM = SMResolver.get();
+    
+    static final Resolver RESOLVER_TS = () -> new FrameArray(Thread.currentThread().getStackTrace(), 4);
+
+    static final Resolver RESOLVER_ES = () -> new FrameArray(new Exception().getStackTrace(), 3); // NOPMD
+
+    /**
+     * if you want full list, SecurityManager is faster than SunReflection 
+     */
+    static final Resolver AUTO_LIST_RESOLVER = ValueUtils.or(
+        RESOLVER_SM, 
+        RESOLVER_SR, 
+        RESOLVER_ES
+    );
+    
+    /**
+     * if you want first element, SunReflection is faster than SecurityManager
+     */
+    static final Resolver AUTO_CALLER_RESOLVER = ValueUtils.or(
+        RESOLVER_SR, 
+        RESOLVER_SM, 
+        RESOLVER_ES
+    );
+
+    private StackInspector() { /* utility class */ }
+    
+    public static List<Class> getCallers() {
+        return AUTO_LIST_RESOLVER.list();
+    }
+    
+    public static List<String> getCallerNames() {
+        return AUTO_LIST_RESOLVER.names();
+    }
+    
+    public static Class getCaller() {
+        return AUTO_CALLER_RESOLVER.firstClass();
+    }
+    
+    public static String getCallerName() {
+        return AUTO_CALLER_RESOLVER.firstName();
+    }
+    
+    @SuppressWarnings("PMD")
+    static List<Class> test_getClassList(Resolver resolver) {
+        return resolver.list();
+    }
+    
+    @SuppressWarnings("PMD")
+    static List<String> test_getClassNames(Resolver resolver) {
+        return resolver.names();
+    }
+    
+    static Class test_getCallerClass(Resolver resolver) {
+        return resolver.firstClass();
+    }
+    
+    static String test_getCallerClassName(Resolver resolver) {
+        return resolver.firstName();
+    }
+
+    interface Resolver {
+        
+        default Class<?> firstClass() {
+            return list().get(1);
+        }
+        
+        default String firstName() {
+            return names().get(1);
+        }
+        
+        default List<Class> list() {
+            throw new UnsupportedOperationException("StackInspector: operation not supported. Please use Sun JDK or enable SecurityManager.");
+        }
+        
+        List<String> names();
+    }
+    
+    private static final class SMResolver extends SecurityManager implements Resolver {
+        
+        private static final int FIRST = 2;
+        
+        @Override
+        public String firstName() {
+            return super.getClassContext()[FIRST].getName();
+        }
+
+        @Override
+        public Class<?> firstClass() {
+            return super.getClassContext()[FIRST];
+        }
+        
+        @Override
+        public List<Class> list() {
+            return new ClassArray(super.getClassContext(), FIRST);
+        }
+
+        @Override
+        public List<String> names() {
+            return new NameArray(super.getClassContext(), FIRST);
+        }
+        
+        public static Resolver get() {
+            try {
+                return new SMResolver();
+            } catch(SecurityException se) {
+                return null;
+            }
+        }
+        
+    }
+    
+    private static final class SunResolver implements Resolver {
+        
+        private static final int FIRST = 3;
+
+        @Override
+        public String firstName() {
+            return sun.reflect.Reflection.getCallerClass(FIRST).getName();
+        }
+
+        @Override
+        public Class<?> firstClass() {
+            return sun.reflect.Reflection.getCallerClass(FIRST);
+        }
+        
+        @Override
+        public List<Class> list() {
+            List<Class> list = new ArrayList<>(64);
+            int i=FIRST;
+            Class<?> c;
+            while(null != (c = sun.reflect.Reflection.getCallerClass(i++))) {
+                list.add(c);
+            }
+            return list;
+        }
+
+        @Override
+        public List<String> names() {
+            List<String> list = new ArrayList<>(64);
+            int i=FIRST;
+            Class<?> c;
+            while(null != (c = sun.reflect.Reflection.getCallerClass(i++))) {
+                list.add(c.getName());
+            }
+            return list;
+        }
+        
+        public static Resolver get() {
+            try {
+                return new SunResolver();
+            } catch(Exception | Error se) {
+                return null;
+            }
+        }
+        
+    }
+
+    private static final class ClassArray extends AbstractList<Class> {
+        
+        private final Class[] array;
+        private final int offset;
+        private final int length;
+
+        @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+        public ClassArray(Class[] array, int offset, int length) {
+            this.array = array;
+            this.offset = offset;
+            this.length = length;
+        }
+        
+        public ClassArray(Class[] array, int offset) {
+            this(array, offset, array.length-offset);
+        }
+
+        @Override
+        public Class get(int index) {
+            return array[offset+index];
+        }
+
+        @Override
+        public int size() {
+            return length;
+        }
+    
+    }
+    
+    private static final class NameArray extends AbstractList<String> {
+        
+        private final Class[] array;
+        private final int offset;
+        private final int length;
+
+        @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+        public NameArray(Class[] array, int offset, int length) {
+            this.array = array;
+            this.offset = offset;
+            this.length = length;
+        }
+        
+        public NameArray(Class[] array, int offset) {
+            this(array, offset, array.length-offset);
+        }
+
+        @Override
+        public String get(int index) {
+            return array[offset+index].getName();
+        }
+
+        @Override
+        public int size() {
+            return length;
+        }
+    
+    }
+    
+    private static final class FrameArray extends AbstractList<String> {
+        
+        private final StackTraceElement[] array;
+        private final int offset;
+        private final int length;
+
+        @SuppressWarnings("PMD.ArrayIsStoredDirectly")
+        public FrameArray(StackTraceElement[] array, int offset, int length) {
+            this.array = array;
+            this.offset = offset;
+            this.length = length;
+        }
+        
+        public FrameArray(StackTraceElement[] array, int offset) {
+            this(array, offset, array.length-offset);
+        }
+
+        @Override
+        public String get(int index) {
+            return array[offset+index].getClassName();
+        }
+
+        @Override
+        public int size() {
+            return length;
+        }
+    
+    }
+    
+}

+ 34 - 0
assira/src/test/java/net/ranides/assira/system/RuntimeUtilsTest.java

@@ -0,0 +1,34 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.system;
+
+import net.ranides.assira.trace.StackInspector;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class RuntimeUtilsTest {
+    
+    public RuntimeUtilsTest() {
+    }
+
+    @Test
+    public void testInvokedInsideMaven() {
+        assertTrue( RuntimeUtils.invokedInsideMaven() );
+    }
+    
+    @Test
+    public void testInvokedInsideSingleTest() {
+        // we have to check that manually
+        System.err.printf("RuntimeUtils.invokedInsideSingleTest = %s%n", RuntimeUtils.invokedInsideSingleTest());
+        assertTrue(true);
+    }
+    
+}

+ 127 - 0
assira/src/test/java/net/ranides/assira/trace/StackInspectorBenchmark.java

@@ -0,0 +1,127 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.trace;
+
+import com.google.caliper.SimpleBenchmark;
+import net.ranides.assira.junit.BenchmarkRunner;
+
+/**
+ * Benchmark for different methods of obtaining list of caller-classess.
+ * 
+ * SecurityManager creates list ~10 times faster than stacktrace.
+ * SecurityManager gets caller  ~15 times faster than stacktrace.
+ * Best if you need all classes.
+ * It's portable, but it won't work if you disable SecurityManager in JVM
+ * 
+ * 
+ * SunReflection creates list ~2 times faster that stacktrace.
+ * SunReflection gets caller  ~100 times faster than stacktrace.
+ * Best if you need first class.
+ * It's nonportable, but it will work even if you disable SecurityManager.
+ * 
+ * 
+ * Notice: JDK8 Stream API is very convenient but it has much too large overhead. 
+ * As effect we removed old "elegant" code. It's unacceptable to sacrifice 25%
+ * of execution time because we want to use functional interfaces.
+ * Streams:
+ *      SecurityManager     =  1.1 us
+ *      SunReflection       =  4.2 us
+ *      Exception           = 10.7 us
+ *      Thread              = 11.3 us
+ * Arrays:
+ *      SecurityManager     =  0.8 us   72 %
+ *      SunReflection       =  3.6 us   85 %
+ *      Exception           = 10.5 us   98 %
+ *      Thread              = 11.1 us   98 %
+ *
+ * 
+ * Most recent results for JDK 1.8.0 (Lenovo G510 ranides-laptop)
+ *
+ *      CallerSunReflect         =   144 ? 2
+ *      CallerSecurityManager    =   822 ? 6
+ *      CallerException          = 12891 ? 81
+ *      CallerThread             = 13668 ? 71
+ * 
+ *      ListSecurityManager      =   842 ? 61
+ *      ListSunReflect           =  3794 ? 24
+ *      ListException            = 12006 ? 44
+ *      ListThread               = 12681 ? 147
+ * 
+ * 
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class StackInspectorBenchmark extends SimpleBenchmark {
+    
+    public int timeListSecurityManager(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getClassNames(StackInspector.RESOLVER_SM).size();
+        }
+        return ret;
+    }
+    
+    public int timeListThread(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getClassNames(StackInspector.RESOLVER_TS).size();
+        }
+        return ret;
+    }
+    
+    public int timeListSunReflect(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getClassNames(StackInspector.RESOLVER_SR).size();
+        }
+        return ret;
+    }
+    
+    public int timeListException(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getClassNames(StackInspector.RESOLVER_ES).size();
+        }
+        return ret;
+    }
+    
+    public int timeCallerSecurityManager(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getCallerClassName(StackInspector.RESOLVER_SM).length();
+        }
+        return ret;
+    }
+    
+    public int timeCallerThread(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getCallerClassName(StackInspector.RESOLVER_TS).length();
+        }
+        return ret;
+    }
+    
+    public int timeCallerSunReflect(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getCallerClassName(StackInspector.RESOLVER_SR).length();
+        }
+        return ret;
+    }
+    
+    public int timeCallerException(int reps) {
+        int ret = 0;
+        for(int i=0; i<reps; i++) {
+            ret += StackInspector.test_getCallerClassName(StackInspector.RESOLVER_ES).length();
+        }
+        return ret;
+    }
+    
+    public static void main(String[] args) {
+        BenchmarkRunner.run(StackInspectorBenchmark.class);
+    }
+    
+}

+ 124 - 0
assira/src/test/java/net/ranides/assira/trace/StackInspectorTest.java

@@ -0,0 +1,124 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/???
+ */
+package net.ranides.assira.trace;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class StackInspectorTest {
+    
+    private static final Class<?> CALLER = StackInspectorTest.class;
+    
+    @Test
+    public void testCallerClass() {
+        Class callerAuto = StackInspector.getCaller();
+        Class callerSM   = StackInspector.test_getCallerClass(StackInspector.RESOLVER_SM);
+        Class callerSR   = StackInspector.test_getCallerClass(StackInspector.RESOLVER_SR);
+                
+        Assert.assertEquals(CALLER, callerAuto);
+        Assert.assertEquals(CALLER, callerSM);
+        Assert.assertEquals(CALLER, callerSR);
+    }
+    
+    @Test
+    public void testCallerClassName() {
+        String callerAuto = StackInspector.getCallerName();
+        String callerSM   = StackInspector.test_getCallerClassName(StackInspector.RESOLVER_SM);
+        String callerSR   = StackInspector.test_getCallerClassName(StackInspector.RESOLVER_SR);
+        String callerTS   = StackInspector.test_getCallerClassName(StackInspector.RESOLVER_TS);
+        String callerES   = StackInspector.test_getCallerClassName(StackInspector.RESOLVER_ES);
+        
+        Assert.assertEquals(CALLER.getName(), callerAuto);
+        Assert.assertEquals(CALLER.getName(), callerSM);
+        Assert.assertEquals(CALLER.getName(), callerSR);
+        Assert.assertEquals(CALLER.getName(), callerTS);
+        Assert.assertEquals(CALLER.getName(), callerES);
+    }
+
+    @Test
+    public void testClassList() {
+        List<Class> listAuto = StackInspector.getCallers();
+        List<Class> listSM   = StackInspector.test_getClassList(StackInspector.RESOLVER_SM);
+        List<Class> listSR   = StackInspector.test_getClassList(StackInspector.RESOLVER_SR);
+        
+        if(false) { //NOPMD
+            dump(listAuto);
+            dump(listSM);
+            dump(listSR);
+        }
+        
+        Assert.assertTrue(
+            listAuto.equals(listSM) ||
+            listAuto.equals(listSR)
+        );
+
+        Assert.assertEquals(listSM, listSR);
+        
+        Assert.assertTrue(listSM.size() > 5);
+        
+        Assert.assertEquals(CALLER, listSM.get(0));
+        Assert.assertEquals(CALLER, listSR.get(0));
+        
+        Assert.assertFalse(CALLER.equals(listSM.get(1)));
+        Assert.assertFalse(CALLER.equals(listSR.get(1)));
+    }
+
+    @Test
+    public void testClassNames() {
+        List<String> listAuto = StackInspector.getCallerNames();
+        List<String> listSM  = StackInspector.test_getClassNames(StackInspector.RESOLVER_SM);
+        List<String> listSR  = StackInspector.test_getClassNames(StackInspector.RESOLVER_SR);
+        List<String> listTS  = StackInspector.test_getClassNames(StackInspector.RESOLVER_TS);
+        List<String> listES  = StackInspector.test_getClassNames(StackInspector.RESOLVER_ES);
+        
+        if(false) { //NOPMD
+            dump(listAuto);
+            dump(listSM);
+            dump(listSR);
+            dump(listTS);
+            dump(listES);
+        }
+        
+        Assert.assertTrue(
+            listAuto.equals(listSM) ||
+            listAuto.equals(listSR) ||
+            listAuto.equals(listTS) ||
+            listAuto.equals(listES) 
+        );
+        
+        Assert.assertEquals(listSM, listSR);
+        Assert.assertEquals(listTS, listES);
+        
+        Assert.assertTrue(listSM.size() > 5);
+        Assert.assertTrue(listSM.size() <= listTS.size());
+        
+        Assert.assertEquals(CALLER.getName(), listSM.get(0));
+        Assert.assertEquals(CALLER.getName(), listSR.get(0));
+        Assert.assertEquals(CALLER.getName(), listTS.get(0));
+        Assert.assertEquals(CALLER.getName(), listES.get(0));
+        
+        Assert.assertFalse(CALLER.getName().equals(listSM.get(1)));
+        Assert.assertFalse(CALLER.getName().equals(listSR.get(1)));
+        Assert.assertFalse(CALLER.getName().equals(listTS.get(1)));
+        Assert.assertFalse(CALLER.getName().equals(listES.get(1)));
+    }
+    
+    @SuppressWarnings("PMD")
+    private static void dump(List<?> list) {
+        int i = 0;
+        for(Object c : list) {
+            System.err.printf("%d. %s%n", i++, c);
+        }
+        System.out.printf("%n");
+    }
+    
+}