Autofixture: Issue when creating value types - autofixture

Debugging with xUnit.net the test-methods Test1 and Test2 of the following code and putting a breakpoint and the end of CreateValueAndReferenceType() you see that the variable valueType is the same in both runs, whereas the variable referenceType is altered. The former is for me surprising and an issue as well (I added the row with the string-type only for completeness).
public class MyFixture : Fixture
{
public void CreateValueAndReferenceType()
{
var valueType = this.Create<int>();
var referenceTye = this.Create<string>();
}
}
public class TestClass1
{
[Fact]
public void Test1()
{
var myFixture = new MyFixture();
myFixture.CreateValueAndReferenceType();
}
}
public class TestClass2
{
[Fact]
public void Test2()
{
var myFixture = new MyFixture();
myFixture.CreateValueAndReferenceType();
}
}

What you're seeing is, I think, a basic issue related to pseudo-random number generation in .NET (IIRC, other platforms have similar issues). In essence, System.Random is deterministic, but initialised with a random seed that, among other things, depend on the computer's current time. If you create instances of Random in a tight loop, the code executes faster than the precision of the system clock. Something like this:
for (int i = 0; i < 10; i++)
Console.Write(new Random().Next(0, 9));
will often produce output like this:
5555555555
Most values in AutoFixture are generated by various Random instances - the exception is the string type, of which values are generated by Guid.NewGuid().ToString().
I think that the reason you're seeing this is because of xUnit.net's parallel execution.
In order to pinpoint the problem, I rephrased the issue so that it doesn't rely on debugging or inheritance:
public static class Reporter
{
public static void CreateValueAndReferenceType(
IFixture fixture,
ITestOutputHelper #out)
{
var valueType = fixture.Create<int>();
var referenceTye = fixture.Create<string>();
#out.WriteLine("valueType: {0}", valueType);
#out.WriteLine("referenceType: {0}", referenceTye);
}
}
public class TestClass1
{
private readonly ITestOutputHelper #out;
public TestClass1(ITestOutputHelper #out)
{
this.#out = #out;
}
[Fact]
public void Test1()
{
Reporter.CreateValueAndReferenceType(new Fixture(), this.#out);
}
}
public class TestClass2
{
private readonly ITestOutputHelper #out;
public TestClass2(ITestOutputHelper #out)
{
this.#out = #out;
}
[Fact]
public void Test2()
{
Reporter.CreateValueAndReferenceType(new Fixture(), this.#out);
}
}
When you run this with the xUnit.net console runner, you can see the issue nicely reproduced:
$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.StackOverflow.Q37925109.dll -diagnostics
-parallel all
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
Discovering: Ploeh.StackOverflow.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
Discovered: Ploeh.StackOverflow.Q37925109 (running 2 test cases)
Starting: Ploeh.StackOverflow.Q37925109 (parallel test collections = on, max threads = 4)
Ploeh.StackOverflow.Q37925109.TestClass2.Test2 [PASS]
Output:
valueType: 246
referenceType: cc39f570-046a-4a0a-8adf-ab7deadd0e26
Ploeh.StackOverflow.Q37925109.TestClass1.Test1 [PASS]
Output:
valueType: 246
referenceType: 87455351-03f7-4640-99fb-05af910da267
Finished: Ploeh.StackOverflow.Q37925109
=== TEST EXECUTION SUMMARY ===
Ploeh.StackOverflow.Q37925109 Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,429s
In the above example, you'll notice that I've explicitly invoked the runner with -parallel all, but I didn't have to do that, since it's the default.
If, on the other hand, you turn off parallelisation with -parallel none, you'll see that the values are different:
$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.StackOverflow.Q37925109.dll -diagnostics
-parallel none
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
Discovering: Ploeh.StackOverflow.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
Discovered: Ploeh.StackOverflow.Q37925109 (running 2 test cases)
Starting: Ploeh.StackOverflow.Q37925109 (parallel test collections = off, max threads = 4)
Ploeh.StackOverflow.Q37925109.TestClass2.Test2 [PASS]
Output:
valueType: 203
referenceType: 1bc75a33-5542-4d9f-b42d-57ed85dc418d
Ploeh.StackOverflow.Q37925109.TestClass1.Test1 [PASS]
Output:
valueType: 117
referenceType: 6a508699-dc35-4bcd-8a7b-15eba64b24b4
Finished: Ploeh.StackOverflow.Q37925109
=== TEST EXECUTION SUMMARY ===
Ploeh.StackOverflow.Q37925109 Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,348s
What I think happens is that because of the parallelism, both Test1 and Test2 are executed in parallel, and essentially within the same tick.
One workaround is to place both tests in the same test class:
public class TestClass1
{
private readonly ITestOutputHelper #out;
public TestClass1(ITestOutputHelper #out)
{
this.#out = #out;
}
[Fact]
public void Test1()
{
Reporter.CreateValueAndReferenceType(new Fixture(), this.#out);
}
[Fact]
public void Test2()
{
Reporter.CreateValueAndReferenceType(new Fixture(), this.#out);
}
}
This produces two different integer values, because (IIRC) xUnit.net only run different test classes in parallel:
$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.StackOverflow.Q37925109.dll -diagnostics
-parallel all
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
Discovering: Ploeh.StackOverflow.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
Discovered: Ploeh.StackOverflow.Q37925109 (running 2 test cases)
Starting: Ploeh.StackOverflow.Q37925109 (parallel test collections = on, max threads = 4)
Ploeh.StackOverflow.Q37925109.TestClass1.Test2 [PASS]
Output:
valueType: 113
referenceType: e8c30ad8-f2c8-4767-9e9f-69b55c50e659
Ploeh.StackOverflow.Q37925109.TestClass1.Test1 [PASS]
Output:
valueType: 232
referenceType: 3eb60bf3-4d43-4a91-aef2-42f7e23e35b3
Finished: Ploeh.StackOverflow.Q37925109
=== TEST EXECUTION SUMMARY ===
Ploeh.StackOverflow.Q37925109 Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,360s
This theory is also corroborated by the fact that if you repeat the experiment sufficiently many times, you'll see the numbers being different once in a while. Here are the integer results from 25 test runs:
33 33
92 92
211 211
13 13
9 9
160 160
55 55
155 155
137 137
161 161
242 242
183 183
237 237
151 151
104 104
254 254
123 123
244 244
144 144
223 9
196 196
126 126
199 199
221 221
132 132
Notice that all except one test run has equal numbers.

Related

Declare a queue with x-max-length programmatically using Rabbitmq-c

I am implementing a RPC function for my C application , and try to programmatically declare a queue which limits maximum number of pending messages, after reading the declaration of amqp_table_entry_t and amqp_field_value_t in amqp.h , here's my minimal code sample :
int default_channel_id = 1;
int passive = 0;
int durable = 1;
int exclusive = 0;
int auto_delete = 0;
amqp_table_entry_t *q_arg_n_elms = malloc(sizeof(amqp_table_entry_t));
*q_arg_n_elms = (amqp_table_entry_t) {.key = amqp_cstring_bytes("x-max-length"),
.value = {.kind = AMQP_FIELD_KIND_U32, .value = {.u32 = 234 }}};
amqp_table_t q_arg_table = {.num_entries=1, .entries=q_arg_n_elms};
amqp_queue_declare( conn, default_channel_id, amqp_cstring_bytes("my_queue_123"),
passive, durable, exclusive, auto_delete, q_arg_table );
amqp_rpc_reply_t _reply = amqp_get_rpc_reply(conn);
The code above always returns AMQP_RESPONSE_LIBRARY_EXCEPTION in the object of amqp_rpc_reply_t, with error message a socket error occurred , I don't see any active connection triggered by this code in web management UI of the RabbitMQ. so I think rabbitmq-c library doesn't establish a connection and just reply with error.
However everything works perfectly when I replace the argument q_arg_table with default amqp_empty_table (which means no argument).
Here are my questions :
Where can I find the code which filter the invalid key of the queue argument ? according to this article , x-max-length should be correct argument key for limiting number of messages in a queue , but I cannot figure out why the library still reports error.
Is there any example that demonstrates how to properly set up amqp_table_t passing in amqp_queue_declare(...) ?
Development environment :
RabbitMQ v3.2.4
rabbitmq-c v0.11.0
Appreciate any feedback , thanks for reading.
[Edit]
According to the server log rabbit#myhostname-sasl.log, RabbitMQ broker accepted a new connection, found decode error on receiving frame, then close connection immediately. I haven't figured out the Erlang implementation but the root cause is likely the decoding error on the table argument when declaring the queue.
131 =CRASH REPORT==== 18-May-2022::16:05:46 ===
132 crasher:
133 initial call: rabbit_reader:init/2
134 pid: <0.23706.1>
135 registered_name: []
136 exception error: no function clause matching
137 rabbit_binary_parser:parse_field_value(<<105,0,0,1,44>>) (src/rabbit_binary_parser.erl, line 53)
138 in function rabbit_binary_parser:parse_table/1 (src/rabbit_binary_parser.erl, line 44)
139 in call from rabbit_framing_amqp_0_9_1:decode_method_fields/2 (src/rabbit_framing_amqp_0_9_1.erl, line 791)
140 in call from rabbit_command_assembler:process/2 (src/rabbit_command_assembler.erl, line 85)
141 in call from rabbit_reader:process_frame/3 (src/rabbit_reader.erl, line 688)
142 in call from rabbit_reader:handle_input/3 (src/rabbit_reader.erl, line 738)
143 in call from rabbit_reader:recvloop/2 (src/rabbit_reader.erl, line 292)
144 in call from rabbit_reader:run/1 (src/rabbit_reader.erl, line 273)
145 ancestors: [<0.23704.1>,rabbit_tcp_client_sup,rabbit_sup,<0.145.0>]
146 messages: [{'EXIT',#Port<0.31561>,normal}]
147 links: [<0.23704.1>]
148 dictionary: [{{channel,1},
149 {<0.23720.1>,{method,rabbit_framing_amqp_0_9_1}}},
150 {{ch_pid,<0.23720.1>},{1,#Ref<0.0.20.156836>}}]
151 trap_exit: true
152 status: running
153 heap_size: 2586
154 stack_size: 27
155 reductions: 2849
156 neighbours:
RabbitMQ may not support unsigned integers as table values.
Instead try using a signed 32 or 64-bit number (e.g., .value = {.kind = AMQP_FIELD_KIND_I32, .value = {.i32 = 234 }}).
Also the RabbitMQ server logs may contain additional debugging information that can help understand errors like this as well as the amqp_error_string2 function can be used to translate error-code into an error-string.

MAX Partitioning in ETL using SSIS

I have a query in SQL Server where I am using MAX OVER Partition BY.
MAX(Duration) OVER (PARTITION BY [Variable8]
ORDER BY [RouterCallKeySequenceNumber] ASC) AS MaxDuration
I would like to implement this in ETL using SSIS.
To implement, I have tried to implement similar to how we can implement Row Number.
I have added a SORT transformation and sorted by Variable8 and RouterCallKeySequenceNumber and then I have added a Script transformation.
string _variable8 = "";
int _max_duration;
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
_max_duration = Row.Duration;
if (Row.Variable8 != _variable8)
{
_max_duration = Row.Duration;
Row.maxduration = _max_duration;
_variable8 = Row.Variable8;
}
else
{
if (Row.Duration >= _max_duration)
{
Row.maxduration = _max_duration;
}
}
}
This is the data that I have -
Variable8 RouterCallKeySequenceNumber Duration
153084-2490 0 265
153084-2490 1 161
153084-2490 2 197
The solution that I need is as below -
Variable8 RouterCallKeySequenceNumber Duration Max Duration
153084-2490 0 265 265
153084-2490 1 161 265
153084-2490 2 197 265
But this does not return the desired value.
I would appreciate if you can provide any help.
Thanks

My H2/C3PO/Hibernate setup does not seem to preserving prepared statements?

I am finding my database is the bottleneck in my application, as part of this it looks like Prepared statements are not being reused.
For example here method I use
public static CoverImage findCoverImageBySource(Session session, String src)
{
try
{
Query q = session.createQuery("from CoverImage t1 where t1.source=:source");
q.setParameter("source", src, StandardBasicTypes.STRING);
CoverImage result = (CoverImage)q.setMaxResults(1).uniqueResult();
return result;
}
catch (Exception ex)
{
MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
}
return null;
}
But using Yourkit profiler it says
com.mchange.v2.c3po.impl.NewProxyPreparedStatemtn.executeQuery() Count 511
com.mchnage.v2.c3po.impl.NewProxyConnection.prepareStatement() Count 511
and I assume that the count for prepareStatement() call should be lower, ais it is looks like we create a new prepared statment every time instead of reusing.
https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html
I am using C3po connecting poolng wehich complicates things a little, but as I understand it I have it configured correctly
public static Configuration getInitializedConfiguration()
{
//See https://www.mchange.com/projects/c3p0/#hibernate-specific
Configuration config = new Configuration();
config.setProperty(Environment.DRIVER,"org.h2.Driver");
config.setProperty(Environment.URL,"jdbc:h2:"+Db.DBFOLDER+"/"+Db.DBNAME+";FILE_LOCK=SOCKET;MVCC=TRUE;DB_CLOSE_ON_EXIT=FALSE;CACHE_SIZE=50000");
config.setProperty(Environment.DIALECT,"org.hibernate.dialect.H2Dialect");
System.setProperty("h2.bindAddress", InetAddress.getLoopbackAddress().getHostAddress());
config.setProperty("hibernate.connection.username","jaikoz");
config.setProperty("hibernate.connection.password","jaikoz");
config.setProperty("hibernate.c3p0.numHelperThreads","10");
config.setProperty("hibernate.c3p0.min_size","1");
//Consider that if we have lots of busy threads waiting on next stages could we possibly have alot of active
//connections.
config.setProperty("hibernate.c3p0.max_size","200");
config.setProperty("hibernate.c3p0.max_statements","5000");
config.setProperty("hibernate.c3p0.timeout","2000");
config.setProperty("hibernate.c3p0.maxStatementsPerConnection","50");
config.setProperty("hibernate.c3p0.idle_test_period","3000");
config.setProperty("hibernate.c3p0.acquireRetryAttempts","10");
//Cancel any connection that is more than 30 minutes old.
//config.setProperty("hibernate.c3p0.unreturnedConnectionTimeout","3000");
//config.setProperty("hibernate.show_sql","true");
//config.setProperty("org.hibernate.envers.audit_strategy", "org.hibernate.envers.strategy.ValidityAuditStrategy");
//config.setProperty("hibernate.format_sql","true");
config.setProperty("hibernate.generate_statistics","true");
//config.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
//config.setProperty("hibernate.cache.use_second_level_cache", "true");
//config.setProperty("hibernate.cache.use_query_cache", "true");
addEntitiesToConfig(config);
return config;
}
Using H2 1.3.172, Hibernate 4.3.11 and the corresponding c3po for that hibernate version
With reproducible test case we have
HibernateStats
HibernateStatistics.getQueryExecutionCount() 28
HibernateStatistics.getEntityInsertCount() 119
HibernateStatistics.getEntityUpdateCount() 39
HibernateStatistics.getPrepareStatementCount() 189
Profiler, method counts
GooGooStaementCache.aquireStatement() 35
GooGooStaementCache.checkInStatement() 189
GooGooStaementCache.checkOutStatement() 189
NewProxyPreparedStatement.init() 189
I don't know what I shoud be counting as creation of prepared statement rather than reusing an existing prepared statement ?
I also tried enabling c3p0 logging by adding a c3p0 logger ands making it use same log file in my LogProperties but had no effect.
String logFileName = Platform.getPlatformLogFolderInLogfileFormat() + "songkong_debug%u-%g.log";
FileHandler fe = new FileHandler(logFileName, LOG_SIZE_IN_BYTES, 10, true);
fe.setEncoding(StandardCharsets.UTF_8.name());
fe.setFormatter(new com.jthink.songkong.logging.LogFormatter());
fe.setLevel(Level.FINEST);
MainWindow.logger.addHandler(fe);
Logger c3p0Logger = Logger.getLogger("com.mchange.v2.c3p0");
c3p0Logger.setLevel(Level.FINEST);
c3p0Logger.addHandler(fe);
Now that I have eventually got c3p0Based logging working and I can confirm the suggestion of #Stevewaldman is correct.
If you enable
public static Logger c3p0ConnectionLogger = Logger.getLogger("com.mchange.v2.c3p0.stmt");
c3p0ConnectionLogger.setLevel(Level.FINEST);
c3p0ConnectionLogger.setUseParentHandlers(false);
Then you get log output of the form
24/08/2019 10.20.12:BST:FINEST: com.mchange.v2.c3p0.stmt.DoubleMaxStatementCache ----> CACHE HIT
24/08/2019 10.20.12:BST:FINEST: checkoutStatement: com.mchange.v2.c3p0.stmt.DoubleMaxStatementCache stats -- total size: 347; checked out: 1; num connections: 13; num keys: 347
24/08/2019 10.20.12:BST:FINEST: checkinStatement(): com.mchange.v2.c3p0.stmt.DoubleMaxStatementCache stats -- total size: 347; checked out: 0; num connections: 13; num keys: 347
making it clear when you get a cache hit. When there is no cache hit yo dont get the first line, but get the other two lines.
This is using C3p0 9.2.1

Sagemaker Hyperparameter Optimization XGBoost

I am trying to build a hyperparameter optimization job in Amazon Sagemaker, in python, but something is not working. Here is what I have:
sess = sagemaker.Session()
xgb = sagemaker.estimator.Estimator(containers[boto3.Session().region_name],
role,
train_instance_count=1,
train_instance_type='ml.m4.4xlarge',
output_path=output_path_1,
base_job_name='HPO-xgb',
sagemaker_session=sess)
from sagemaker.tuner import HyperparameterTuner, IntegerParameter, CategoricalParameter, ContinuousParameter
hyperparameter_ranges = {'eta': ContinuousParameter(0.01, 0.2),
'num_rounds': ContinuousParameter(100, 500),
'num_class': 4,
'max_depth': IntegerParameter(3, 9),
'gamma': IntegerParameter(0, 5),
'min_child_weight': IntegerParameter(2, 6),
'subsample': ContinuousParameter(0.5, 0.9),
'colsample_bytree': ContinuousParameter(0.5, 0.9)}
objective_metric_name = 'validation:mlogloss'
objective_type='minimize'
metric_definitions = [{'Name': 'validation-mlogloss',
'Regex': 'validation-mlogloss=([0-9\\.]+)'}]
tuner = HyperparameterTuner(xgb,
objective_metric_name,
objective_type,
hyperparameter_ranges,
metric_definitions,
max_jobs=9,
max_parallel_jobs=3)
tuner.fit({'train': s3_input_train, 'validation': s3_input_validation})
And the error I get is:
AttributeError: 'str' object has no attribute 'keys'
The error seems to come from the tuner.py file:
----> 1 tuner.fit({'train': s3_input_train, 'validation': s3_input_validation})
~/anaconda3/envs/python3/lib/python3.6/site-packages/sagemaker/tuner.py in fit(self, inputs, job_name, **kwargs)
144 self.estimator._prepare_for_training(job_name)
145
--> 146 self._prepare_for_training(job_name=job_name)
147 self.latest_tuning_job = _TuningJob.start_new(self, inputs)
148
~/anaconda3/envs/python3/lib/python3.6/site-packages/sagemaker/tuner.py in _prepare_for_training(self, job_name)
120
121 self.static_hyperparameters = {to_str(k): to_str(v) for (k, v) in self.estimator.hyperparameters().items()}
--> 122 for hyperparameter_name in self._hyperparameter_ranges.keys():
123 self.static_hyperparameters.pop(hyperparameter_name, None)
124
AttributeError: 'list' object has no attribute 'keys'
Your arguments when initializing the HyperparameterTuner object are in the wrong order. The constructor has the following signature:
HyperparameterTuner(estimator,
objective_metric_name,
hyperparameter_ranges,
metric_definitions=None,
strategy='Bayesian',
objective_type='Maximize',
max_jobs=1,
max_parallel_jobs=1,
tags=None,
base_tuning_job_name=None)
so in this case, your objective_type is in the wrong position. See the docs for more details.

Java API to find out the JDK version a class file is compiled for?

Are there any Java APIs to find out the JDK version a class file is compiled for? Of course there is the javap tool to find out the major version as mentioned in here. However I want to do it programmatically so that that I could warn the user to compile it for the appropriate JDK
import java.io.*;
public class ClassVersionChecker {
public static void main(String[] args) throws IOException {
for (int i = 0; i < args.length; i++)
checkClassVersion(args[i]);
}
private static void checkClassVersion(String filename)
throws IOException
{
DataInputStream in = new DataInputStream
(new FileInputStream(filename));
int magic = in.readInt();
if(magic != 0xcafebabe) {
System.out.println(filename + " is not a valid class!");;
}
int minor = in.readUnsignedShort();
int major = in.readUnsignedShort();
System.out.println(filename + ": " + major + " . " + minor);
in.close();
}
}
The possible values are :
major minor Java platform version
45 3 1.0
45 3 1.1
46 0 1.2
47 0 1.3
48 0 1.4
49 0 5
50 0 6
51 0 7
52 0 8
53 0 9
54 0 10
55 0 11
56 0 12
57 0 13
58 0 14
59 0 15
60 0 16
61 0 17
62 0 18
63 0 19
ref : The Java Virtual Machine Specification, Java SE 19 Edition
basszero's approach can be done via the UNIX command line, and the "od(1)" command:
% od -x HelloWorldJava.class |head -2
0000000 feca beba 0000 3100 dc00 0007 0102 2a00
0000020 6f63 2f6d 6e65 6564 6163 642f 6d65 2f6f
"feca beba" is the magic number. The "0000 3100" is 0x31, which represents J2SE 5.0.
On Linux, you can use the file command on a class file, which I found easiest for my use case. For example:
$ file Foo.class
Foo.class: compiled Java class data, version 50.0 (Java 1.6)
Apache BCEL provides this API:
JavaClass c = Repository.lookupClass("com.x.MyClass")
c.getMinor();
c.getMajor();
This gets you the contents of the class file:
MysteryClass.class.getResourceAsStream("MysteryClass.class")
Then look at bytes 5-8 to get the minor and major version. A mapping between those numbers and the JDK releases can be found here.
Just read the class file directly. It's VERY easy to figure out the version. Check out the the spec and wiki and then try the code. Wrapping this code and making is more useful/pretty is left as an exercise. Alternatively, you could use a library like BCEL, ASM, or JavaAssist
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ClassVersionTest
{
private static final int JAVA_CLASS_MAGIC = 0xCAFEBABE;
public static void main(String[] args)
{
try
{
DataInputStream dis = new DataInputStream(new FileInputStream("Test.class"));
int magic = dis.readInt();
if(magic == JAVA_CLASS_MAGIC)
{
int minorVersion = dis.readUnsignedShort();
int majorVersion = dis.readUnsignedShort();
/**
* majorVersion is ...
* J2SE 6.0 = 50 (0x32 hex),
* J2SE 5.0 = 49 (0x31 hex),
* JDK 1.4 = 48 (0x30 hex),
* JDK 1.3 = 47 (0x2F hex),
* JDK 1.2 = 46 (0x2E hex),
* JDK 1.1 = 45 (0x2D hex).
*/
System.out.println("ClassVersionTest.main() " + majorVersion + "." + minorVersion);
}
else
{
// not a class file
}
}
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
As others have shown, it is easy enough to do by reading the first eight bytes of a class file. If you want a pre-built binary library, you can download one here.
JavaP does have an API, but it's specific to the Sun JDK.
It's found in tools.jar, under sun/tools/javap/Main.class.
I use this script in a batch file on windows, redirect the output and grep for the major number I am verifying for:
for /R %%f in (*.class) do (
"C:\Program Files\Java\JDK8\bin\javap" -v "%%f" | findstr major )
I run it as such:
verify.bat > versionResults.txt

Resources