I am accustomed to java.io.* and java.util.* but not to the tree:
com.starbase.util
Class FileUtils
java.lang.Object
|
+--com.starbase.util.FileUtils
Source.
So which class should I import to use the isBinary-method? Do I do "import java.lang.Object;" or "import java.lang.Object.com.starbase.util.FileUtils;"?
You would do import com.starbase.util.FileUtils; or import static com.starbase.util.FileUtils.*. The hierarchy is just showing that the class FileUtils extends Object (as do all classes).
You do also have to have the .jar file/API to access this class.
EDIT: Added possible standalone implementation:
If you want to implement this yourself (I noticed your own 'trivial' answer), you could do something like this:
public static boolean isBinary(String fileName) throws IOException {
return isBinary(new File(fileName));
}
public static boolean isBinary(File file) throws IOException {
InputStream is = new FileInputStream(file);
try {
byte[] buf = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buf)) >= 0)
{
for (int i = 0; i < bytesRead; i++) {
if (buf[i] == (byte) 0)
return true;
}
}
return false;
} finally {
is.close();
}
}
Please note, I have not tested this.
I should add that this is the trivial implementation. There are many kinds of text files that would be considered binary with this. If you were to allow text to be Unicode and/or UTF-8 (or other text encoding) then this quickly becomes very difficult. Then you need to develop some kinds of heuristics to differentiate between the kinds of files and this would not be 100% accurate. So, it really depends on what you are trying to do with this.
You never have to import java.lang.Object, it's imported implicitely and is the class that every other class is derived from. When you import another class, you import it based on the package it's in. So for the class you want to use it would be:
import com.starbase.util.FileUtils;
com.starbase.util.FileUtils is not in the standard packaged Java SDK, but instead the StarTeam SDK which you would need to download in order to use the FileUtils#isBinary method.
Once installed, you would just need to add:
import com.starbase.util.FileUtils;
However, if you do not want to use a third-party SDK, let us know how isBinary would be helpful to you and we could find a standard Java equivalent.
Also to clarify, import.java.lang.Object.com.starbase.util.FileUtils is not a valid import, you are concatenating two different packages together.
It has to be either import java.lang.Object or import com.starbase.util.FileUtils.
Totally trivial and perhaps easiest but very unreliable!
if( filename.toLowerCase().trim().endsWith(".bin"))
return "Binary";
Related
I learn how to use java 8 API. I have a simple log file with the following contents:
SVF2018-05-24_12:02:58.917
NHR2018-05-24_12:02:49.914
FAM2018-05-24_12:13:04.512
KRF2018-05-24_12:03:01.250
SVM2018-05-24_12:18:37.735
MES2018-05-24_12:04:45.513
LSW2018-05-24_12:06:13.511
BHS2018-05-24_12:14:51.985
EOF2018-05-24_12:17:58.810
RGH2018-05-24_12:05:14.511
SSW2018-05-24_12:16:11.648
KMH2018-05-24_12:02:51.003
PGS2018-05-24_12:07:23.645
CSR2018-05-24_12:03:15.145
SPF2018-05-24_12:12:01.035
DRR2018-05-24_12:14:12.054
LHM2018-05-24_12:18:20.125
CLS2018-05-24_12:09:41.921
VBM2018-05-24_12:00:00.000
My goal is to parse it using streams. The desired output is the following:
[{SVF = [2018-05-24, 12:02:58.917]}, {NHR = [2018-05-24, 12:02:49.914]}...]
I already have the following:
public class FileParser {
Stream<String> outputStream;
public FileParser(String fileName) throws IOException, URISyntaxException {
FileReader fr = new FileReader();
this.outputStream = fr.getStreamFromFile(fileName);
public List<HashMap<String,ArrayList<String>>> getRacersInfo(){
return outputStream.map(line -> Arrays.asList(line.substring(0,3))
.collect(Collectors.toMap(???)); //Some code here which I cannot come up with.
}
Any help appreciated. If you need any additional information feel free to ask, I'll be glad to provide it.
Problem: FileReader
FileReader is obsolete, don't use it. It's outdated API, and it's problematic, in that it presumes 'platform default encoding' which is a different way of saying 'a bug waiting to happen that no test will catch but that will blow up in your face later'. You never want 'platform default encoding', especially as a silent default.
There's a new File API, and it lets you specify encoding explicitly. Also, in the new File API, if you don't, UTF-8 is assumed which is a far saner default than 'platform default'.
Problem: resources
Resources are objects that represent a resource that takes up OS-level handles. Files, network connections, database connections - those are some common examples of resources. The thing is unlike normal objects, you MUST explicitly CLOSE those. - if you don't, your VM will, eventually, crash. That means you can basically not put readers/inputstreams/outputstreams/writers in fields, ever, because how do you guarantee closing them? The only way is to make your own class a resource too (a thing that must explicitly be closed), which you can do, but is complicated, and not a good idea here.
You should never make resources unless you do so safely:
bad:
FileReader fr = new FileReader(..);
good:
try (FileReader fr = new FileReader(..)) {
// use here
}
// it's gone here
It does require you to restyle things a bit. You have to open a resource, use the resource, and close it. This meshes well with pragmatic concerns: Resources are a drain on the OS, you don't want to keep em open any longer than you must, so 'open it, use it, and lose it' is the right mindset.
Furthermore, of course, resources as a concept are generally 'once-through-only'. for example, when reading a file, well, you read it, once, from the top to the bottom, and then any further attempts to read from it don't work anymore. So, in your example, the first time I call getRacersInfo(), it works. But the second time I call it, it won't, as the reader has now been consumed.
The solution to both problems is to do the reading in the constructor*.
*) See later - we're going to move this out of the constructor eventually, but that's a separate concern.
Problem: Misunderstanding of responsibilities of constructors
This class is called a FileParser. So, it's job is to parse files (that, or, this class has a bad name). Generally, your constructors represent the 'data gathering' phase, not the 'do the job' phase. Therefore, parsing the file in the first place, in that constructor, is bad code style. You should not do this - your constructors should as a rule do as little as possible and definitely nothing tricky, such as opening files or actually parsing things. Again - the JOB of a FileParser is to parse files, and constructors should not do the job. They just set up the object so that it can do the job later.
The proper design, then, is:
public class FileParser {
private final Path path;
public FileParser(Path path) {
this.path = path;
}
public List<Map<String, List<String>> parseRacersInfo() {
try (Stream<String> lines = Files.lines(path)) {
lines.map(.... parse the content here ....);
}
}
}
We have now:
Moved the 'job' part to a method that accurately describes the job.
Ensured the constructor is simple and just gathers information to do the job.
Safely use resources by applying the try(){} concept.
Use the new API (java.nio.file.Files and java.nio.file.Path).
Clarified our typing: That parameter to the constructor represents a path. If I call new FileParser("Hello, IceTea, how's life?") - that call makes no sense. Path is more descriptive than String, and if your method makes sense looking only at the types of the parameters? That's better than if you need to read the docs too.
Problem: Not using java the way it wants to be used
Java is typed. Nominally so. Things should be stored in types that represent that thing. Thus, the string 2018-05-24_12:18:20.125 should be represented by an object that represents a time of some sort. Not a List<String> containing the string 2018-05-24 and 12:18:20.125.
Finally: How do I actually write the mapping?
Streams work by zooming in on a single element in the stream, and doing a series of operations on these elements, transforming them, filtering some out, etcetera. You cannot 'go back' in the process (once you map a thing to another thing, you can't go back to what it used to be), and you can't refer to other objects in your stream (you can't ask: Give me the item before me in the stream).
Thus, once you go: line.substring(0, 3), you've thrown out the date, and that's a problem because we need that info. Therefore, you can't do that; not in a .map() operation, at any rate.
In fact, we can go straight to collecting the stream back into a map here - we need that entire string and we can derive the key from it (SVF), and we need that entire string and we can derive the value from it (the date).
Let's write these conversion functions, and let's translate our string representing a time to a proper (also new in java 8) type for it: java.time.LocalDateTime:
Function<String, String> toKey = in -> in.substring(0, 3);
DateTimeFormatter DATETIME_FORMAT =
DateTimeFormatter.ofPattern("uuuu-MM-dd_HH:mm:ss.SSS", Locale.ENGLISH);
Function<String, LocalDateTime> toValue = in ->
LocalDateTime.parse(in.substring(3), DATETIME_FORMAT);
These are simple and we can test them:
assertEquals("VBM", toKey.apply("VBM2018-05-24_12:00:00.000"));
assertEquals(LocalDateTime.of(2018, 5, 24, 12, 0, 0),
toValue.apply("VBM2018-05-24_12:00:00.000"));
Then we put it all together:
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileParser {
private static final DateTimeFormatter DATETIME_FORMAT =
DateTimeFormatter.ofPattern("uuuu-MM-dd_HH:mm:ss.SSS", Locale.ENGLISH);
private final Path path;
public FileParser(Path path) {
this.path = path;
}
public Map<String, LocalDateTime> parseRacersInfo() throws IOException {
try (Stream<String> lines = Files.lines(path)) {
return lines.collect(Collectors.toMap(
in -> in.substring(0, 3),
in -> LocalDateTime.parse(in.substring(3), DATETIME_FORMAT)));
}
}
public static void main(String[] args) throws Exception {
System.out.println(new FileParser("test.txt").parseRacersInfo());
}
}
Path path = Paths.get(fileName);
try (Stream<String> lines = Files.lines(path)) {
Map<String, List<LocalDateTime>> map = lines.collect(Collectors.groupingBy(
line -> line.substring(0, 3),
line -> LocalDateTime.parse(line.substring(3).replace('_', 'T')));
}
The toMap receives a key mapper and a value mapper. Here I keep the Stream of lines.
The resulting map is just a Map. Never provide an implementation, HashMap so the collect may return its own implementation. (If effect you could provide an implementation.)
(I used Files.lines which defaults to UTF-8 encoding, but you can add an encoding. The reason: Path is more generalized than File.)
Something like :
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
String fileName = "C:\\Users\\Asmir\\Desktop\\input1.txt";
Map<String,List<String>> map = new HashMap<>();
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
map = stream
.collect(Collectors.toMap(s -> s.substring(0,3), s -> Arrays.asList(s.substring(3).split("_"))));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(map);
}
}
Is there a command line tool that can automatically fix non formatting but still seemingly simple CheckStyle issues in Java source code like:
Avoid inline conditionals
Make "xxx" a static method
I know there are various tools to fix formatting and some IDEs have fairly advanced quick fixers but so far I could not find anything that can recursively run on a source code folder or be integrated in a commit hook.
Sounds like a nice challenge, but I was also unable to find an automatic tool that can do this. As you already described, there are plenty of options to change code formatting. For other small issues, you could perhaps run Checkstyle from the command-line and filter out fixable warnings. A library for parsing and changing Java source code could help to actually make the changes, like for example JavaParser. Perhaps you could write a custom tool in a relatively small amount of time using a Java source code manipulation tool like JavaParser.
(There are other tools like ANTLR that could be used; see for more ideas this question on Stack Overflow: Java: parse java source code, extract methods. Some libraries like Roaster and JavaPoet do not parse the body of methods, which makes them less suitable in this situation.)
As a very simple example, assume we have a small Java class for which Checkstyle generates two messages (with a minimalistic checkstyle-checks.xml Checkstyle configuration file that only checks FinalParameters and FinalLocalVariable):
// Example.java:
package q45326752;
public class Example {
public static void main(String[] arguments) {
System.out.println("Hello Checkstyle...");
int perfectNumber = 1 + 2 + 3;
System.out.println("Perfect number: " + perfectNumber);
}
}
Checkstyle warnings:
java -jar checkstyle-8.0-all.jar -c checkstyle-checks.xml Example.java
[ERROR] Example.java:4:29: Parameter arguments should be final. [FinalParameters]
[ERROR] Example.java:7:13: Variable 'perfectNumber' should be declared final. [FinalLocalVariable]
Using JavaParser, these two warnings could be fixed automatically like this (the code tries to demonstrate the idea; some parts have been ignored for now):
// AutomaticCheckstyleFix.java:
package q45326752;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import java.io.File;
import java.io.FileNotFoundException;
public class AutomaticCheckstyleFix {
private MethodDeclaration bestMatchMethod;
private int bestMatchMethodLineNumber;
private Statement statementByLineNumber;
public static void main(final String[] arguments) {
final String filePath = "q45326752\\input\\Example.java";
try {
new AutomaticCheckstyleFix().fixSimpleCheckstyleIssues(new File(filePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private void fixSimpleCheckstyleIssues(File file) throws FileNotFoundException {
CompilationUnit javaClass = JavaParser.parse(file);
System.out.println("Original Java class:\n\n" + javaClass);
System.out.println();
System.out.println();
// Example.java:4:29: Parameter arguments should be final. [FinalParameters]
MethodDeclaration methodIssue1 = getMethodByLineNumber(javaClass, 4);
if (methodIssue1 != null) {
methodIssue1.getParameterByName("arguments")
.ifPresent(parameter -> parameter.setModifier(Modifier.FINAL, true));
}
// Example.java:7:13: Variable 'perfectNumber' should be declared final.
// [FinalLocalVariable]
Statement statementIssue2 = getStatementByLineNumber(javaClass, 7);
if (statementIssue2 instanceof ExpressionStmt) {
Expression expression = ((ExpressionStmt) statementIssue2).getExpression();
if (expression instanceof VariableDeclarationExpr) {
((VariableDeclarationExpr) expression).addModifier(Modifier.FINAL);
}
}
System.out.println("Modified Java class:\n\n" + javaClass);
}
private MethodDeclaration getMethodByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
bestMatchMethod = null;
javaClass.getTypes().forEach(type -> type.getMembers().stream()
.filter(declaration -> declaration instanceof MethodDeclaration)
.forEach(method -> {
if (method.getTokenRange().isPresent()) {
int methodLineNumber = method.getTokenRange().get()
.getBegin().getRange().begin.line;
if (bestMatchMethod == null
|| (methodLineNumber < issueLineNumber
&& methodLineNumber > bestMatchMethodLineNumber)) {
bestMatchMethod = (MethodDeclaration) method;
bestMatchMethodLineNumber = methodLineNumber;
}
}
})
);
return bestMatchMethod;
}
private Statement getStatementByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
statementByLineNumber = null;
MethodDeclaration method = getMethodByLineNumber(javaClass, issueLineNumber);
if (method != null) {
method.getBody().ifPresent(blockStmt
-> blockStmt.getStatements().forEach(statement
-> statement.getTokenRange().ifPresent(tokenRange -> {
if (tokenRange.getBegin().getRange().begin.line == issueLineNumber) {
statementByLineNumber = statement;
}
})));
}
return statementByLineNumber;
}
}
Another approach could be to create new Checkstyle plugins based on the ones you are trying to create an automatic fix for. Perhaps you have enough information available to not only give a warning but to also generate a modified version with these issues fixed.
Personally I would hesitate to have issues fixed automatically upon commit. When there are many simple fixes to be made, automation is welcome, but I would like to check these changes before committing them. Running a tool like this and checking the changes could be a very fast way to fix a lot of simple issues.
Some checks that I think could be fixed automatically:
adding static
fixing inline conditionals
FinalParameters and FinalLocalVariable: adding final
ModifierOrder: reordering modifiers (example: final static private)
NeedBraces: adding braces
I need to programmatically find out which JRE classes can be referenced in a compilation unit without being imported (for static code analysis). We can disregard package-local classes. According to the JLS, classes from the package java.lang are implicitly imported. The output should be a list of binary class names. The solution should work with plain Java 5 and up (no Guava, Reflections, etc.), and be vendor agnostic.
Any reliable Java-based solution is welcome.
Here are some notes on what I've tried so far:
At first glance, it seems that the question boils down to "How to load all classes from a package?", which is of course practically impossible, although several workarounds exist (e.g. this and this, plus the blog posts linked there). But my case is much simpler, because the multiple classloaders issue does not exist. java.lang stuff can always be loaded by the system/bootstrap classloader, and you cannot create your own classes in that package. Problem is, the system classloader does not divulge its class path, which the linked appoaches rely on.
So far, I haven't managed to get access to the system classloader's class path, because on the HotSpot VM I'm using, Object.class.getClassLoader() returns null, and Thread.currentThread().getContextClassLoader() can load java.lang.Object by delegation, but does not itself include the classpath. So solutions like this one don't work for me. Also, the list of guaranteed system properties does not include properties with this kind of classpath info (such as sun.boot.class.path).
It would be nice if I didn't have to assume that there is an rt.jar at all, and rather scan the list of resources used by the system classloader. Such an approach would be safer with respect to vendor specific JRE implementations.
Compiled classes appear to contain readable java/lang text. So I wrote a little bit of code to see if these imports can be extracted. It's a hack, so not reliable, but assuming you can extract/list all classes in a jar-file, this could be a starting point.
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Q21102294 {
public static final String EXTERNAL_JAR = "resources/appboot-1.1.1.jar";
public static final String SAMPLE_CLASS_NAME = "com/descartes/appboot/AppBoot.class";
public static HashSet<String> usedLangClasses = new HashSet<String>();
public static void main(String[] args) {
try {
Path f = Paths.get(EXTERNAL_JAR);
if (!Files.exists(f)) {
throw new RuntimeException("Could not find file " + f);
}
URLClassLoader loader = new URLClassLoader(new URL[] { f.toUri().toURL() }, null);
findLangClasses(loader, SAMPLE_CLASS_NAME);
ArrayList<String> sortedClasses = new ArrayList<String>();
sortedClasses.addAll(usedLangClasses);
Collections.sort(sortedClasses);
System.out.println("Loaded classes: ");
for (String s : sortedClasses) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void findLangClasses(URLClassLoader loader, String classResource) throws Exception {
URL curl = loader.getResource(classResource);
if (curl != null) {
System.out.println("Got class as resource.");
} else {
throw new RuntimeException("Can't open resource.");
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
InputStream in = curl.openStream();
try {
byte[] buf = new byte[8192];
int l = 0;
while ((l = in.read(buf)) > -1) {
bout.write(buf, 0, l);
}
} finally {
in.close();
}
String ctext = new String(bout.toByteArray(), StandardCharsets.UTF_8);
int offSet = -1;
while ((offSet = ctext.indexOf("java/lang/", offSet)) > -1) {
int beginIndex = offSet;
offSet += "java/lang/".length();
char cnext = ctext.charAt(offSet);
while (cnext != ';' && (cnext == '/' || Character.isAlphabetic(cnext))) {
offSet += 1;
cnext = ctext.charAt(offSet);
}
String langClass = ctext.substring(beginIndex, offSet);
//System.out.println("adding class " + langClass);
usedLangClasses.add(langClass);
}
}
}
Gives the following output:
Got class as resource.
Loaded classes:
java/lang/Class
java/lang/ClassLoader
java/lang/Exception
java/lang/Object
java/lang/RuntimeException
java/lang/String
java/lang/StringBuilder
java/lang/System
java/lang/Thread
java/lang/Throwable
java/lang/reflect/Method
Source code of the used compiled class is available here.
OK, I misread the question. Checking the JLS, all I see is:
"Every compilation unit implicitly imports every public type name declared in the predefined package java.lang, as if the declaration import java.lang.*; appeared at the beginning of each compilation unit immediately after any package statement. As a result, the names of all those types are available as simple names in every compilation unit."
(http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html)
If you want to know which types that includes, it's going to vary from version to version of Java...
A program i am working on deals with processing file content. Right now i am writing jUnit tests to make sure things work as expected. As part of these tests, i'd like to reference an inline text file which would define the scope of a particular test.
How do i access such a file?
--
Let me clarify:
Normally, when opening a file, you need to indicate where the file is. What i want to say instead is "in this project". This way, when someone else looks at my code, they too will be able to access the same file.I may be wrong, but isn't there a special way, one can access files which are a part of "this" project, relative to "some files out there on disk".
If what you mean is you have a file you need your tests to be able to read from, if you copy the file into the classpath your tests can read it using Class.getResourceAsStream().
For an example try this link (Jon Skeet answered a question like this):
read file in classpath
You can also implement your own test classes for InputStream or what have you.
package thop;
import java.io.InputStream;
/**
*
* #author tonyennis
*/
public class MyInputStream extends InputStream {
private char[] input;
private int current;
public MyInputStream(String s) {
input = s.toCharArray();
current = 0;
}
public int read() {
return (current == input.length) ? -1 : input[current++];
}
#Override
public void close() {
}
}
This is a simple InputStream. You give it a string, it gives you the string. If the code you wanted to test required an InputStream, you could use this (or something like it, heh) to feed exactly the strings wanted to test. You wouldn't need resources or disk files.
Here I use my lame class as input to a BufferedInputStream...
package thop;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
*
* #author tonyennis
*/
public class Main {
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException {
InputStream is = new MyInputStream("Now is the time");
BufferedInputStream bis = new BufferedInputStream(is);
int res;
while((res = bis.read()) != -1) {
System.out.println((char)res);
}
}
}
Now, if you want to make sure your program parses the inputStream correctly, you're golden. You can feed it the string you want to test with no difficulty. If you want to make sure the class being tested always closes the InputStream, add a "isOpen" boolean instance variable, set it to true in the constructor, set it to false in close(), and add a getter.
Now your test code would include something like:
MyInputStream mis = new MyInputStream("first,middle,last");
classBeingTested.testForFullName(mis);
assertFalse(mis.isOpen());
I have a class in C++ which takes an std::ostream as an argument in order to continuously output text (trace information). I need to get this text over to the Java side as efficiently as possible. What's the best way to do this? I was thinking of using a direct buffer, but another method would be to take all the function calls across to Java and do all the processing there, but it seems that I'd need a lot of JNI calls.
If an example could be shown of the exact implementation method, it would be very helpful, or if some code exists already to do this (perhaps part of another project). Another help would be to connect it up directly to a standard Java streaming construct, such that the entire implementation was completely transparent to the developer.
(Edit: I found Sharing output streams through a JNI interface which seems to be a duplicate, but not really of much help -- he didn't seem to find the answer he was looking for)
The std::ostream class requires a std::streambuf object for its output. This is used by the fstream and stringstream classes, which use the features of ostream by providing a custom implementation of the streambuf class.
So you can write your own std::streambuf implementation with an overwritten overflow method, buffer the incomming chars in an internal stringbuffer. Every x calls or on eof/newline generate an java-string and call the print method of your java PrintStream.
An incomplete example class:
class JavaStreamBuff : std::streambuf
{
std::stringstream buff;
int size;
jobject handle;
JNIEnv* env
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuff(JNIEnv* env, jobject jobject printStream, int buffsize = 50)
{
handle = env->NewGlobalRef(printStream);
this->env = env;
this->size = size;
}
//This method is the central output of the streambuf class, every charakter goes here
int overflow(int in)
{
if(in == eof || buff.size() == size)
{
std::string blub = buff.str();
jstring do = //magic here, convert form current locale unicode then to java string
jMethodId id = env->(env->GetObjectClass(handle),"print","(java.lang.String)V");
env->callVoidMethod(id,handle,do);
buff.str("");
}
else
{buff<<in;}
}
virtual ~JavaStreamBuff()
{
env->DeleteGlobalRef(handle);
}
}
Missing:
Multithread support (the env pointer is only valid for the jvm thread)
Error handling (checking for java exceptions thrown)
Testing(written within the last 70 min)
Native java method to set the printstream.
On the java side you need a class to convert the PrintStream to a BufferedReader.
There have to be some bugs there, haven't spend enough time to work on them.
The class requires all access to be from the thread it was created in.
Hope this helps
Note
I got it to work with visual studio but I can't get it to work with g++, will try to debug that later.
Edit
Seems that I should have looked for a more official tutorial on this bevore posting my answer, the MSDN page on this topic derives the stringbuffer in a different way.
Sorry for posting this without testing it better :-(.
A small correction to the code above in a more or less unrelated point: Just implement InputStream with a custom class and push byte[] arrays instead of Strings from c++.
The InputStream has a small interface and a BufferedReader should do most of the work.
Last update on this one, since im unable to get it to work on linux, even with the comments on the std::streambuf class stating that only overflow has to be overwritten.
This implementation pushes the raw strings into an inputstream, which can be read from by an other thread. Since I am too stupid to get the debugger working its untested, again.
//The c++ class
class JavaStreamBuf :public std::streambuf
{
std::vector<char> buff;
unsigned int size;
jobject handle;
JNIEnv* env;
public:
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuf(JNIEnv* env, jobject cppstream, unsigned int buffsize = 50)
{
handle = env->NewGlobalRef(cppstream);
this->env = env;
this->size = size;
this->setbuf(0,0);
}
//This method is the central output of the streambuf class, every charakter goes here
virtual int_type overflow(int_type in = traits_type::eof()){
if(in == std::ios::traits_type::eof() || buff.size() == size)
{
this->std::streambuf::overflow(in);
if(in != EOF)
buff.push_back(in);
jbyteArray o = env->NewByteArray(buff.size());
env->SetByteArrayRegion(o,0,buff.size(),(jbyte*)&buff[0]);
jmethodID id = env->GetMethodID(env->GetObjectClass(handle),"push","([B)V");
env->CallVoidMethod(handle,id,o);
if(in == EOF)
env->CallVoidMethod(handle,id,NULL);
buff.clear();
}
else
{
buff.push_back(in);
}
return in;
}
virtual ~JavaStreamBuf()
{
overflow();
env->DeleteGlobalRef(handle);
}
//The java class
/**
*
*/
package jx;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* #author josefx
*
*/
public class CPPStream extends InputStream {
List<Byte> data = new ArrayList<Byte>();
int off = 0;
private boolean endflag = false;
public void push(byte[] d)
{
synchronized(data)
{
if(d == null)
{
this.endflag = true;
}
else
{
for(int i = 0; i < d.length;++i)
{
data.add(d[i]);
}
}
}
}
#Override
public int read() throws IOException
{
synchronized(data)
{
while(data.isEmpty()&&!endflag)
{
try {
data.wait();
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
if(endflag)return -1;
else return data.remove(0);
}
}
Sorry for wasting so much space^^(and time :-().
It sounds as though the deliverable here is a subclass of ostream. The immediate question I'd want to be clear about is, will this class be responsible for buffering data until Java calls into it to retrieve, or is it expected to immediately (synchronously?) call via JNI to pass it on? That will be the strongest guide to how the code will shape up.
If you can reasonably expect the text to appear as a series of lines, I'd think about presenting them to Java in one line per call: this seems a fair compromise between the number of JNI calls and not unduly delaying the passing on of the text.
On the Java side I think you're looking at creating a Reader so that clients can pick up the text via a familiar interface, or perhaps a subclass of BufferedReader.