javascalasvggephi

Scala Java code sees SVG only on second execution (NullPointerException: SVG does not exist)


this is my first Stack Overflow question, so I hope everything is alright: I create a GEXF file in Scala, convert it to a SVG graph (with the Gephi toolkit), and convert the results once more to PNG file format. The creation of the GEXF file is done in Scala, and the SVG and PNG conversion in Java.

The script runs flawless, when I first create the GEXF file and afterwards start the SVG and PNG conversion. I can do this with the if-functions: I first set if (1) on true and if (2) on false. Second: I set if (1) on false and if (2) on true.

When I run the code with both ifs on true, I get a NullPointerException that the SVG does not exist. Note: I have uploaded the code to GitHub for a better overview and rebuild of the error: https://github.com/commutativity/svg-problem

  val conf: SparkConf = new SparkConf().setAppName("SparkTest").setMaster("local[*]").set("spark.executor.memory", "16g")
  val sparkContext = new SparkContext(conf)
  val sparkSession: SparkSession = SparkSession.builder.config(sparkContext.getConf)  .config().getOrCreate()
  val sqlContext: SQLContext = sparkSession.sqlContext


  if (true) {    // if (1)
    val v = sqlContext.createDataFrame(List(
      ("a", "Alice", 34),
      ("b", "Bob", 36),
    )).toDF("id", "name", "age")

    val e = sqlContext.createDataFrame(List(
      ("a", "b", "friend"),
    )).toDF("src", "dst", "relationship")

    val g = GraphFrame(v, e)
    val subgraphX = g.toGraphX
    val pw = new PrintWriter("src\\main\\resources\\gexf\\" + "friends" + ".gexf")
    val gexfString = toGexf(subgraphX)
    pw.write(gexfString)
    pw.close()
  }


  if (true) {     // if (2)
    // import java classes
    class ScalaDriver extends JavaDriver {
      runGEXFtoSVG("friends", "friends")

      runSVGtoPNG("src\\main\\resources\\svg\\friends.svg",
        "src\\main\\resources\\png\\friends.png")
    }

    // run imported java classes
    new ScalaDriver
  }


  def toGexf[VD, ED](g: Graph[VD, ED]): String = {
    val header =
      """<?xml version="1.0" encoding="UTF-8"?>
        |<gexf xmlns="https://www.gexf.net/1.2draft" version="1.2">
        |<meta>
        |<description>A gephi graph in GEXF format</description>
        |</meta>
        |<graph mode="static" defaultedgetype="directed">
        |<attributes class="node">
        |<attribute id="1" title="redirect" type="string"/>
        |<attribute id="2" title="namespace" type="string"/>
        |<attribute id="3" title="category" type="string"/>
        |</attributes>
      """.stripMargin


    val vertices = "<nodes>\n" + g.vertices.map(
      v => s"""<node id=\"${v._1}\" label=\"${v._2.asInstanceOf[Row].getAs("id")}\">\n
      </node>"""
    ).collect.mkString + "</nodes>\n"

    val edges = "<edges>\n" + g.edges.map(
      e =>
        s"""<edge source=\"${e.srcId}\" target=\"${e.dstId}\"
  label=\"${e.attr}\"/>\n"""
    ).collect.mkString + "</edges>\n"

    val footer = "</graph>\n</gexf>"

    header + vertices + edges + footer
  }

The error message looks like follows:

java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at graph.GEXFtoSVG.script(GEXFtoSVG.java:46)
    at graph.JavaDriver.runGEXFtoSVG(JavaDriver.java:7)
    at utils.ReproduceError$ScalaDriver$1.<init>(ReproduceError.scala:38)
    at utils.ReproduceError$.delayedEndpoint$utils$ReproduceError$1(ReproduceError.scala:45)
    at utils.ReproduceError$delayedInit$body.apply(ReproduceError.scala:11)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1$adapted(App.scala:80)
    at scala.collection.immutable.List.foreach(List.scala:431)
    at scala.App.main(App.scala:80)
    at scala.App.main$(App.scala:78)
    at utils.ReproduceError$.main(ReproduceError.scala:11)
    at utils.ReproduceError.main(ReproduceError.scala)

Exception in thread "main" org.apache.batik.transcoder.TranscoderException: null
Enclosed Exception:
File file:/D:/scala_wiki/sbt-test1/src/main/resources/svg/friends.svg does not exist
    at org.apache.batik.transcoder.XMLAbstractTranscoder.transcode(XMLAbstractTranscoder.java:136)
    at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:156)
    at graph.SVGtoPNG.createImage(SVGtoPNG.java:35)
    at graph.SVGtoPNG.<init>(SVGtoPNG.java:19)
    at graph.JavaDriver.runSVGtoPNG(JavaDriver.java:11)
    at utils.ReproduceError$ScalaDriver$1.<init>(ReproduceError.scala:41)
    at utils.ReproduceError$.delayedEndpoint$utils$ReproduceError$1(ReproduceError.scala:45)
    at utils.ReproduceError$delayedInit$body.apply(ReproduceError.scala:11)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1$adapted(App.scala:80)
    at scala.collection.immutable.List.foreach(List.scala:431)
    at scala.App.main(App.scala:80)
    at scala.App.main$(App.scala:78)
    at utils.ReproduceError$.main(ReproduceError.scala:11)
    at utils.ReproduceError.main(ReproduceError.scala)

The JavaDriver classes contains:

    public void runGEXFtoSVG(String gexfName, String svgName) throws Exception {
        GEXFtoSVG graph = new GEXFtoSVG();
        graph.script(gexfName, svgName);
    }

    public void runSVGtoPNG(String svgName, String pngName) throws Exception {
        new SVGtoPNG(svgName, pngName);
    }

The GEXFtoSVG class contains:

public class GEXFtoSVG {

    public void script(String gexfName, String svgName) throws Exception {
        ProjectController pc = Lookup.getDefault().lookup(ProjectController.class);
        pc.newProject();
        Workspace workspace = pc.getCurrentWorkspace();

        //Get models and controllers for this new workspace - will be useful later
        GraphModel graphModel = Lookup.getDefault().lookup(GraphController.class).getModel();
        PreviewModel model = Lookup.getDefault().lookup(PreviewController.class).getModel();
        ImportController importController = Lookup.getDefault().lookup(ImportController.class);

        Container container;
        try {
            File file = new File(Objects.requireNonNull(getClass().getResource(String.format("/gexf/%s.gexf", gexfName))).toURI());
            container = importController.importFile(file);
            container.getLoader().setEdgeDefault(EdgeDefault.DIRECTED);
        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }

        // import the container into the workspace. The workspace contains the graph model and model
        importController.process(container, new DefaultProcessor(), workspace);

        //Layout for 1 second
        AutoLayout autoLayout = new AutoLayout(1, TimeUnit.SECONDS);
        autoLayout.setGraphModel(graphModel);
        YifanHuLayout firstLayout = new YifanHuLayout(null, new StepDisplacement(1f));
        ForceAtlasLayout secondLayout = new ForceAtlasLayout(null);
        AutoLayout.DynamicProperty adjustBySizeProperty = AutoLayout
                .createDynamicProperty("forceAtlas.adjustSizes.name", Boolean.TRUE, 0.1f);
        //True after 10% of layout time
        AutoLayout.DynamicProperty repulsionProperty = AutoLayout
                .createDynamicProperty("forceAtlas.repulsionStrength.name", 500., 0f);
        //500 for the complete period
        autoLayout.addLayout(firstLayout, 0.5f);
        autoLayout.addLayout(secondLayout, 0.5f, new AutoLayout
                .DynamicProperty[]{adjustBySizeProperty, repulsionProperty});
        autoLayout.execute();

        // Preview
        model.getProperties().putValue(PreviewProperty.SHOW_NODE_LABELS, Boolean.TRUE);
        model.getProperties().putValue(PreviewProperty.EDGE_COLOR, new EdgeColor(Color.BLACK));
        model.getProperties().putValue(PreviewProperty.EDGE_THICKNESS, 0.1f);
        model.getProperties().putValue(PreviewProperty.SHOW_EDGES, Boolean.TRUE);
        model.getProperties().putValue(PreviewProperty.DIRECTED, true);
        model.getProperties().putValue(PreviewProperty.ARROW_SIZE, 20.0f);
        model.getProperties().putValue(PreviewProperty.EDGE_CURVED, false);
        model.getProperties().putValue(PreviewProperty.NODE_LABEL_FONT, new Font("Arial", Font.PLAIN, 2));

        //Export
        ExportController ec = Lookup.getDefault().lookup(ExportController.class);
        try {
            ec.exportFile(new File(String.format("src\\main\\resources\\svg\\%s.svg", svgName)));
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        container.closeLoader();
    }
}

And the SVGtoPNG class contains:

public class SVGtoPNG {
    String svgDirAndName;
    String pngDirAndName;

    SVGtoPNG(String svgDirAndName, String pngDirAndName) throws Exception {
        this.svgDirAndName =svgDirAndName;
        this.pngDirAndName =pngDirAndName;
        createImage();
    }

    public void createImage() throws Exception {
        String svg_URI_input = new File(String.format("%s", svgDirAndName)).toURI().toString();
        TranscoderInput input_svg_image = new TranscoderInput(svg_URI_input);

        // define OutputStream to PNG Image and attach to TranscoderOutput
        OutputStream png_ostream = Files.newOutputStream(Paths.get(pngDirAndName));
        TranscoderOutput output_png_image = new TranscoderOutput(png_ostream);

        // create PNGTranscoder and define hints if required
        PNGTranscoder my_converter = new PNGTranscoder();

        // convert and Write output
        System.out.println("It will print");
        my_converter.transcode(input_svg_image, output_png_image);
        System.out.println("It will not print");
        png_ostream.flush();
        png_ostream.close();
    }
}

I have tried inserting a wait-function between the two if-constructs, however, this did not solve the error.

The full example is available on GitHub. https://github.com/commutativity/svg-problem

Please make sure to include the libraries "batik-codec-1.16" and the "gephi-toolkit-0.8.7" as JARs. I have included the links to download the JARs.

Any help, suggestions, and recommendations, are highly appreciated.


Solution

  • During the build process, the contents of your source resource folder are copied into your target directory along with the compiled .class files (and then eventually into the generated .jar - if you're generating a .jar). When you run the compiled program, getResource will load the resources from the target directory (or from the .jar if you're running a .jar), not the source directory.

    So adding files to the source resources directory will not have any effect on getResources (not to mention that, outside of development, programs are usually run in an environment where the source directory isn't even available). You could, technically, make it work by writing to the target directory instead of the source one, but then it would still only work when running the program directly from the target directory - not from a .jar.

    Really, you should use getResources only to access files that exist at build time. In order to read files generated at runtime, you should use normal file path and IO classes and methods, not getResources.