简体   繁体   中英

Processing.org 3 and loading Java properties file?

I am not all that knowledgeable in Java, and I'm trying to use some Java in a Processing.org 3 project. I have managed to reconstruct the problem in a small Processing example project called testprocjavapath - and I am posting a bash script (called testProcJavaLoadpath.sh ) that reconstructs the example project files at the end of this post, and runs the project once. The testprocjavapath project files look like this:

~/sketchbook/testprocjavapath
├── testprocjavapath.pde
├── myprops.properties
└── MyJavaClass.java

When running the script, I get this:

$ bash testProcJavaLoadpath.sh
...
There was an exception myprops.properties: java.lang.NullPointerException : null
The properties file content is 'null';
Finished.

Debugging in Processing 3 IDE GUI, this error occurs on exactly the line properties.load(in); :

Processing-testprocjavapath.png

... because the line InputStream in = MyJavaClass.class.getResourceAsStream(inFileName); failed, and as a result, in is a null pointer.

That much I understand - what I don't understand is this: how do I load a, say, .properties text file, in the same directory as the .pde Processing sketch file and the .java file (that is, this particular sketch folder)?

As far as I gather the Java getResourceAsStream() actually is used to load from a Java application packed as a .jar file - so can it work for reading files from hard disk, that are not yet packed as .jar files?

If not - I have also tried to do:

InputStream in = new FileInputStream( new File(PROPERTIES_FILENAME));

... but this didn't work either ( in is again null ).

So, what command can I use in the .java file, to load the myprops.properties file? And if I should end up packing the whole Processing app as a .jar file (not sure if Processing can do this, haven't looked it up yet), would I have to change that command?


Here is the testProcJavaLoadpath.sh file (make sure you change PROCBINPATH to your Processing install path):

PROCSKETCHDIR="~/sketchbook"
PROCSKETCHDIR="${PROCSKETCHDIR/#\~/$HOME}" # expand home dir ~
echo "$PROCSKETCHDIR"
PROCBINPATH="/PATH/TO/processing-3.3.6" # path/location of Processing executable `processing-java`

MYSKETCH="testprocjavapath"
MYSKETCHDIR="$PROCSKETCHDIR/$MYSKETCH"
# reconstruct folder:
rm -rfv "$MYSKETCHDIR"
mkdir -v "$MYSKETCHDIR"

echo "generating $MYSKETCHDIR/$MYSKETCH.pde"
cat > "$MYSKETCHDIR/$MYSKETCH.pde" <<'EOF'

void setup() {
  size(640, 360);  // Size should be the first statement
  MyJavaClass myjc = new MyJavaClass();
  String thefilecontents = myjc.GetPropsFileContent();
  System.out.format("The properties file content is '%s';%n", thefilecontents);
}

EOF

echo "generating $MYSKETCHDIR/myprops.properties"
cat > "$MYSKETCHDIR/myprops.properties" <<'EOF'
teststr=HelloWorld
EOF

echo "generating $MYSKETCHDIR/MyJavaClass.java"
cat > "$MYSKETCHDIR/MyJavaClass.java" <<'EOF'
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.io.FileInputStream;
import java.io.File;
import java.io.ByteArrayOutputStream;

public class MyJavaClass {

  private static final String PROPERTIES_FILENAME = "myprops.properties";

  /**
   * add a constructor
   */
  public static void MyJavaClass() {
  }

  public static String GetPropsFileContent() {
    String myret = null;
    myret = readgetFileContent(PROPERTIES_FILENAME);
    return myret;
  }

  public static String readgetFileContent(String inFileName) {
    String result = null;
    Properties properties = new Properties();
    try {
      InputStream in = MyJavaClass.class.getResourceAsStream(inFileName);
      properties.load(in);

      ByteArrayOutputStream resultbaos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int length;
      while ((length = in.read(buffer)) != -1) {
        resultbaos.write(buffer, 0, length);
      }
      result = resultbaos.toString();
    } catch (IOException e) {
      System.err.println("There was an error reading " + inFileName + ": " + e.getCause()
          + " : " + e.getMessage());
    } catch (Exception e) {
      System.err.println("There was an exception " + inFileName + ": " + e
          + " : " + e.getMessage());
    }
    return result;
  }
}
EOF

# run once:
"$PROCBINPATH"/processing-java --sketch="$MYSKETCHDIR" --run

Ok, I managed to get somewhere - but I'd still like a more qualified answer.

First of all, it turns out, Processing as such expect files to be read (like myprops.properties in the OP example), to be stored in a data subfolder of the sketch folder:

https://processing.org/tutorials/data/

And just as with image files, these text files should be placed in the sketch's “data” directory in order for them to be recognized by the Processing sketch.

So far so good - and indeed, inside the Processing .pde sketch, we can use (say) loadStrings("myprops.properties"); , and the file at data/myprops.properties will be read. However, I don't need to read the file there - I need to read it in the supporting .java class.

Now, when you run the Processing patch (either from IDE, or from the command line), what happens is that Processing copies the source files from the sketch folder, inside a temporary folder in the /tmp folder (at least on Linux); here's how that file structure looks like:

/tmp/testprocjavapath9179591342074530534temp/
├── MyJavaClass.class
├── source
│   ├── MyJavaClass.java
│   └── testprocjavapath.java
└── testprocjavapath.class

Notice that we have .java source files, and .class "compiled" files, but there is no data subfolder or myprops.properties file anywhere!

Now, notice also that what used to be testprocjavapath.pde in the source sketch folder, becomes testprocjavapath.java (and corresponding .class ) in the temporary folder; notice that testprocjavapath.java defines:

public class testprocjavapath extends PApplet {

Now, the loadStrings is actually a method of the PApplet class; so if we read through it a bit:

https://github.com/processing/processing/blob/master/core/src/processing/core/PApplet.java

dataPath(String where) : .... The data path is handled differently on each platform, and should not be considered a location to write files. It should also not be assumed that this location can be read from or listed. ... Libraries should use createInput() to get an InputStream or createOutput() to get an OutputStream. sketchPath() can be used to get a location relative to the sketch. Again, use this to get relative locations of files. 使用它来获取文件的相对位置。 ...

... we can see a method dataPath , however it's usage is not recommended. On the other hand, there is a method sketchPath - however, this method will only return the correct path (ie a sketch in ~/sketchbook in this example) if called from the top-level .pde file! If you try to have the class in the .java file defined as extends PApplet , and then call sketchPath from there - it will simply return the current working directory!

So the solution for now is:

  • Have the .java class accept an input argument in the constructor, which will be used to record the proper sketch path:
     public MyJavaClass(String inSketchPath) {  
  • Then, have the sketchPath() passed to the .java class instance during instantiation in the .pde file:
     MyJavaClass myjc = new MyJavaClass( sketchPath() );  
  • Finally, use the passed sketch path inside the .java class, to calculate the absolute path of the .properties file, and then load it with new FileInputStream( new File(theFilePath) ); ( not with getResourceAsStream !)

Below a changed testProcJavaLoadpath.sh is pasted, that has these modifications, and in principle, works - this is the terminal output:

$ bash testProcJavaLoadpath.sh
...
Sketch first lines: 'teststr=HelloWorld';
Sketch dataFile: '~/sketchbook/testprocjavapath/data/myprops.properties';
Sketch sketchPath: '~/sketchbook/testprocjavapath';
:: mySketchPath: '~/sketchbook/testprocjavapath'
:: The URL is 'file:/tmp/testprocjavapath4709659129218148940temp/';
:: name: MyJavaClass.class
:: resourcePath: file:/tmp/testprocjavapath4709659129218148940temp/MyJavaClass.class
:: theFilePath: '~/sketchbook/testprocjavapath/data/myprops.properties'
:: properties: key 'teststr' => value 'HelloWorld'
The properties file content is 'teststr=HelloWorld
';

... however, I imagine if I should want to pack this code in a .jar or executable application/file, this approach would likely fail - which is why I'd still like a more qualified answer.

The changed testProcJavaLoadpath.sh is this:

PROCSKETCHDIR="~/sketchbook"
PROCSKETCHDIR="${PROCSKETCHDIR/#\~/$HOME}" # expand home dir ~
echo "$PROCSKETCHDIR"
PROCBINPATH="/PATH/TO/processing-3.3.6" # path/location of Processing executable `processing-java`

MYSKETCH="testprocjavapath"
MYSKETCHDIR="$PROCSKETCHDIR/$MYSKETCH"
# reconstruct folder:
rm -rfv "$MYSKETCHDIR"
mkdir -v "$MYSKETCHDIR"

# https://processing.org/tutorials/data/
# "And just as with image files, these text files should be placed in the sketch’s “data” directory in order for them to be recognized by the Processing sketch."
# processing.core.PApplet.loadStrings - https://processing.github.io/processing-javadocs/core/
# https://github.com/processing/processing/blob/master/core/src/processing/core/PApplet.java
# "dataPath(String where): The data path is handled differently on each platform, and should not be considered a location to write files. It should also not be assumed that this location can be read from or listed. ... Libraries should use createInput() to get an InputStream or createOutput() to get an OutputStream. sketchPath() can be used to get a location relative to the sketch. Again, <b>do not</b> use this to get relative locations of files."

echo "generating $MYSKETCHDIR/$MYSKETCH.pde"
cat > "$MYSKETCHDIR/$MYSKETCH.pde" <<'EOF'

void setup() {
  size(640, 360);  // Size should be the first statement
  String[] lines = loadStrings("myprops.properties"); // reads from data/myprops.properties
  System.out.format("Sketch first lines: '%s';%n", lines[0]);
  System.out.format("Sketch dataFile: '%s';%n", dataFile("myprops.properties")); // ~/sketchbook/testprocjavapath/data/myprops.properties
  System.out.format("Sketch sketchPath: '%s';%n", sketchPath()); // ~/sketchbook/testprocjavapath
  MyJavaClass myjc = new MyJavaClass( sketchPath() );
  String thefilecontents = myjc.GetPropsFileContent();
  System.out.format("The properties file content is '%s';%n", thefilecontents);
}

EOF

mkdir -v "$MYSKETCHDIR/data"
echo "generating $MYSKETCHDIR/data/myprops.properties"
cat > "$MYSKETCHDIR/data/myprops.properties" <<'EOF'
teststr=HelloWorld
EOF

echo "generating $MYSKETCHDIR/MyJavaClass.java"
cat > "$MYSKETCHDIR/MyJavaClass.java" <<'EOF'
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream; // "InputStream is by definition not seekable."
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.io.FileInputStream; // is seekable
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.net.URL;

//import processing.core.*;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MyJavaClass {

  private static final String PROPERTIES_FILENAME = "myprops.properties";
  public String mySketchPath;

  /**
   * add a constructor
   */
  public MyJavaClass(String inSketchPath) {
    mySketchPath = inSketchPath;
  }

  public String GetPropsFileContent() {
    //System.out.format(":: sketchPath: '%s'%n", sketchPath()); // if `MyJavaClass extends PApplet`, then sketchPath() just prints current working directory!
    System.out.format(":: mySketchPath: '%s'%n", mySketchPath);
    getLocations();
    String myret = null;
    myret = readgetFileContent(PROPERTIES_FILENAME);
    return myret;
  }

  public String readgetFileContent(String inFileName) {
    String result = null;
    Properties properties = new Properties();
    try {
      //String theFilePath = inFileName; // verbatim relative path fails
      Path inFileNameSketchPath = Paths.get(mySketchPath, "data", inFileName); // OS path join
      String theFilePath = inFileNameSketchPath.toString();
      System.out.format(":: theFilePath: '%s'%n", theFilePath);

      //InputStream in = MyJavaClass.class.getResourceAsStream(theFilePath); // no can do, is 'null', also w/ abs path
      //InputStream in = new FileInputStream( new File(theFilePath) ); // OK, but not seekable
      FileInputStream in = new FileInputStream( new File(theFilePath) );
      properties.load(in);

      // double-check loaded properties:
      for(String key : properties.stringPropertyNames()) {
        String value = properties.getProperty(key);
        System.out.format(":: properties: key '%s' => value '%s'%n", key, value);
      }

      ByteArrayOutputStream resultbaos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int length;
      in.getChannel().position(0); // do reset - seek 0 (start), to reset stream again for reading
      while ((length = in.read(buffer)) != -1) {
        resultbaos.write(buffer, 0, length);
      }
      result = resultbaos.toString();
    } catch (IOException e) {
      System.err.println("There was an error reading " + inFileName + ": " + e.getCause()
          + " : " + e.getMessage());
    } catch (Exception e) {
      System.err.println("There was an exception " + inFileName + ": " + e
          + " : " + e.getMessage());
    }
    return result;
  }

  public void getLocations() {
    URL classURL = getClass().getProtectionDomain().getCodeSource().getLocation();
    System.out.format(":: The URL is '%s';%n", classURL); // file:/tmp/testprocjavapath3056820301028631180temp/

    String s = getClass().getName();
    int i = s.lastIndexOf(".");
    if(i > -1) s = s.substring(i + 1);
    s = s + ".class";
    System.out.println(":: name: " + s);
    Object resourcePath = this.getClass().getResource(s);
    System.out.println(":: resourcePath: " + resourcePath); // file:/tmp/testprocjavapath9185318125154993853temp/MyJavaClass.class
  }
}
EOF

# run once:
"$PROCBINPATH"/processing-java --sketch="$MYSKETCHDIR" --run

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM