Kategorien
Computer Programmierung

Java-Stream-API

Weil ich gestern das Script hatte: die Stream-API von Java ist schon geil für einige Anwendungsfälle. Kurz, prägnant und lesbar.

Ausschnitt vom Code gestern: einmal Pfad bis in die Unterverzeichnisse traversieren und die Dateien bearbeiten:

try (Stream<Path> paths = Files.walk(pathSource)) {
  paths
    .filter(Files::isRegularFile)
    .filter(path -> path.toString().endsWith(EXTENSION))
    .forEach(path -> processFile(path, args[1], Integer.parseInt(args[2])));
}

Gerade das Filter-Map-Reduce-Pattern macht was her (Map ist im Beispiel nicht dabei).

Aber ich finde es nicht einfach zu lernen und da ist viel try-error und Nachlesen (ich hab mir ein Buch gekauft!) dabei, gerade bei Syntax, Variablen-Zugriff und sonstigen Nebeneffekten.

Die Funktion processFile z.B. darf keine Exception werfen, sondern nur RuntimeException, weil das sonst nicht compiliert. Hat bestimmt seinen Grund, ist aber programmtechnisch blöd, weil nicht mehr so sauber.

Aber irgendwas ist immer…

Kategorien
Computer Programmierung

Java als Scriptsprache

Mal etwas anderes als Filme: Seit Java 11 kann man Java als Scriptsprache einsetzen, das heißt, direkt eine Java-Datei aufrufen.

Im Endeffekt schreibt man eine Klasse mit main-Methode, die die Arbeit erledigt (man kann wohl mehrere Klassen in eine Datei packen, aber wer macht sowas).

Was rauskommt, ist typisches Scripting der ersten Stunde: Spaghetticode, der eine Aufgabe erledigt und nicht wiederverwendet werden kann. Wenn man sich dessen bewusst ist, ist das tatsächlich hilfreich.

Vorteil: das Script ist plattformübergreifend nutzbar
Nachteil: Java ist nicht für Scripting entworfen, der Code ist grauenhaft, es ist nur eine Datei erlaubt, also keine Wiederverwendung

Ich habe das jetzt mal ausprobiert, da ich schnell eine Lösung brauchte und mich in Python etc. immer erst einarbeiten muss und auf meinen Rechnern überall Java installiert ist.

Ob ich das dauerhaft Bash-Scripts (die echt nicht schön aussehen) und Ant-Scripts vorziehe – mal so, mal so, vermute ich. Alles mit Aufruf von Programmen auf dem Rechner wird wohl bash oder Ant bleiben, bei dem Rest muss ich gucken.

Mein aktuelles Beispiel: neuen Dateinamen für eine Datei finden mit Informationen, die aus dem XML (kml) geholt werden. Dafür sieht es ganz ok aus…

Aufruf: java RenameKML.java <path> <color> <width>

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.Writer;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

public class RenameKML {

  private final static String EXTENSION = ".kml";

  /**
   * Main method.
   */
  public static void main(String[] args) {

    System.out.println("Rename KML");

    try {

      if (args.length < 3) {
        throw new Exception("Missing parameters.");
      }

      Path pathSource = Path.of(args[0]);
      if (Files.exists(pathSource)) {
        try (Stream<Path> paths = Files.walk(pathSource)) {
          paths
              .filter(Files::isRegularFile)
              .filter(path -> path.toString().endsWith(EXTENSION))
              .forEach(path -> processFile(path, args[1], Integer.parseInt(args[2])));
        }
      } else {
        throw new FileNotFoundException(pathSource.toString());
      }

    } catch (Exception e) {

      System.err.println();
      e.printStackTrace();
      System.err.println();
      printUsage();

      System.exit(1);

    }

  }

  /**
   * Usage information.
   */
  private static void printUsage() {

    System.out.println("Usage:\tjava RenameKML.java <path> <color> <width>");
    System.out.println("\tcolor: RRGGBBAA");
    System.out.println("");

  }

  /**
   * Process single file.
   *
   * A normal Exception cannot be declared, because this method is called from
   * withni Stream.forEach, only RuntimeException is allowed there.
   */
  private static void processFile(
      final Path theXMLFile,
      final String theColor,
      final int theWidth
  ) {

    System.out.println(String.format("Processing %s with color %s and width %d", theXMLFile.toString(), theColor, theWidth));

    try {

      String sXMLContent = Files.readString(theXMLFile);
      sXMLContent = cleanKML(sXMLContent, theColor, theWidth);


      Path pathOutFile = Path.of(theXMLFile.getParent().toAbsolutePath().toString(), String.format("%s%s", getNewFilename(sXMLContent), EXTENSION));

      writeKMLFile(pathOutFile, sXMLContent);

      if (!theXMLFile.toAbsolutePath().equals(pathOutFile.toAbsolutePath())) {
        System.out.println(String.format("\tDeleting: %s", theXMLFile.toString()));
        Files.delete(theXMLFile);
      }

    } catch (Exception e) {
      throw new RuntimeException(e);
    }

  }

  /**
   * Clean KML from unused or strange tags/content.
   */
  private static String cleanKML(
      final String theKML,
      final String theColor,
      final int theWidth
  ) throws Exception {

    String sKMLColor = theColor.replaceAll("(..)(..)(..)(..)", "$4$3$2$1");

    String sKMLCleaned = theKML;
    // IconStyles
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<Style id=\".*\"><IconStyle>.*?</IconStyle></Style>",
      ""
    );
    // Folder
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<Folder>(.*)</Folder>",
      "$1"
    );
    // Recording Placemarks
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<Placemark>.*?Recording.*?</Placemark>",
      ""
    );
    // Fill name, remove other
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<name>.*<description>(.*)</description>(<Placemark>)",
      "$2<name>$1</name>"
    );
    // Remove double names
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<name>(.*)</name><name>.*?</name>",
      "<name>$1</name>"
    );
    // Remove last name/description
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(</Placemark>)<name>.*</description>",
      "$1"
    );
    // Remove strange underlines
    sKMLCleaned = sKMLCleaned.replaceAll(
      "<u>(.*?)</u>",
      "$1"
    );

    // reformatting description

    // time conversion (strangely complicated)
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(\\d{2}\\:\\d{2}\\:\\d{2}) vorm\\. ",
      "$1 "
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(\\d{1}\\:\\d{2}\\:\\d{2}) vorm\\. ",
      "0$1 "
    );
    Matcher matchTime = Pattern.compile("(\\d{1,2})(\\:\\d{2}\\:\\d{2}) nachm\\. ").matcher(sKMLCleaned);
    while (matchTime.find()) {
      sKMLCleaned = sKMLCleaned.replaceAll(
        String.format("%s%s nachm\\. ", matchTime.group(1), matchTime.group(2)),
        String.format("%d%s ", Integer.parseInt(matchTime.group(1)) + 12, matchTime.group(2))
      );
    }

    // Duration
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(Duration\\: )(\\d{1}h\\d{2}'\\d{2}\")",
      "$10$2"
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(Duration\\: )(\\d{2})h(\\d{2})'(\\d{2})\"",
      "$1$2:$3:$4"
    );
    // Description itself
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(<description><!\\[CDATA\\[)Start\\: (\\d{2}\\.\\d{2}\\.\\d{4}).*?(\\d{2}\\:\\d{2})\\:\\d{2} .*?End\\: \\d{2}\\.\\d{2}\\.\\d{4}.*?(\\d{2}\\:\\d{2})\\:\\d{2} .*?Duration\\: (\\d{2}\\:\\d{2})\\:\\d{2}.*?Length\\: (.*?)km.*?Avg Speed\\: (.*?)km h.*?Max Speed\\: (.*?)km h.*?Low\\: (.*?)m.*?High\\: (.*?)m.*?Gain\\: (.*?)m.*(\\]\\]></description>)",
      "$1Am $2 von $3 bis $4 Uhr, Dauer: $5 h, Strecke: $6 km, Ø: $7 km/h, v_max: $8 km/h, Höhenmeter: $11 m von $9 m bis $10 m$12"
    );

    // /reformatting description

    // Single TimeStamp
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(</styleUrl>)(<gx:Track>)(<altitudeMode>.*</altitudeMode>)(<when>.*?</when>)",
      "$1<TimeStamp>$4</TimeStamp>$2$3"
    );
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(<altitudeMode>.*</altitudeMode>)<when>.*</when>",
      "$1"
    );

    // Change color
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(<color>).{8}(</color>)",
      String.format("$1%s$2", sKMLColor)
    );

    // Change width
    sKMLCleaned = sKMLCleaned.replaceAll(
      "(<width>).(</width>)",
      String.format("$1%d$2", theWidth)
    );

    return sKMLCleaned;

  }

  /**
   * Write KML file, tidy up first.
   */
  private static void writeKMLFile(
      final Path theOutFile,
      final String theXMLContent
  ) throws Exception {

    System.out.println(String.format("\tWriting: %s", theOutFile.toString()));
    Files.writeString(theOutFile, tidyXML(theXMLContent), StandardOpenOption.CREATE, StandardOpenOption.WRITE);

  }

  /**
   * Tidy up XML.
   */
  private static String tidyXML(
      final String theXMLContent
  ) throws Exception {

    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(theXMLContent.getBytes()));
    doc.getDocumentElement().normalize();

    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    DOMSource source = new DOMSource(doc);

    String sResult = null;

    try (Writer swXML = new StringWriter()) {

      transformer.transform(source, new StreamResult(swXML));
      sResult = swXML.toString();

    }

    return sResult;

  }

  /**
   * Get new filename from name and date within KML file.
   */
  private static String getNewFilename(final String theXML) throws Exception {

    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(theXML.getBytes()));
    doc.getDocumentElement().normalize();


    NodeList nList = doc.getElementsByTagName("name");

    if (nList.getLength() != 1) {
      throw new Exception("more than one or no name tag");
    }

    String sName = nList.item(0).getTextContent();


    nList = doc.getElementsByTagName("when");

    if (nList.getLength() != 1) {
      throw new Exception("more than one or no when tag");
    }

    LocalDateTime dteWhen = LocalDateTime.parse(nList.item(0).getTextContent(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);

    return String.format("%s_%s",
      dteWhen.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
      cleanFilename(sName, true)
    );

  }

  /**
   * Clean filename from special characters.
   */
  private static String cleanFilename(
      final String theFilename,
      final boolean convertUmlauts
  ) {

    String sConverted = theFilename;

    sConverted = sConverted
        .replace(" ", "_")
        .replace("?", "_")
        .replace(":", "_")
        .replace(";", "_")
        .replace(",", "_")
        .replace("/", "_")
        .replace("\\", "_")
        .replace("*", "_")
        .replace("(", "_")
        .replace(")", "_")
        .replace("[", "_")
        .replace("]", "_")
        .replace("{", "_")
        .replace("}", "_")
        ;

    if (convertUmlauts) {
      sConverted = sConverted
          .replace("ä", "ae")
          .replace("Ä", "Ae")
          .replace("ö", "oe")
          .replace("Ö", "Oe")
          .replace("ü", "ue")
          .replace("Ü", "Ue")
          .replace("ß", "ss")
          ;
    }

    return sConverted;

  }

}

/* EOF */

Als Vergleich hier das Ant-Script, dass die XML-Umwandlung vornimmt, könnt Ihr ja selbst sehen, was Ihr lesbarer findet…

<?xml version="1.0" encoding="UTF-8" ?>

<project name="kml" default="help" basedir=".">

	<import file="/home/ekleinod/working/git/edgeutils/ant/ant-commons.xml"/>

	<target name="kml" depends="clearLog" description="Clean all KML files in directory.">

		<fail message="Pfad für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:path" />
		<fail message="Farbe für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:color" />
		<fail message="Breite für die KML-Dateien nicht gesetzt (Farbe: RRGGBBTT): ant kml -Dkml:path=&quot;.&quot; -Dkml:color=&quot;14C8F0CE&quot; -Dkml:width=&quot;5&quot;" unless="kml:width" />

		<echo message="Bearbeite KML-Dateien in '${kml:path}', Farbe '${kml:color}', Breite '${kml:width}'" />

		<echo message="Remove IconStyles" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;Style id=&quot;.*&quot;&gt;&lt;IconStyle&gt;.*&lt;/IconStyle&gt;&lt;/Style&gt;"
				replace=""
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove Folder" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;Folder&gt;(.*)&lt;/Folder&gt;"
				replace="\1"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove Recording Placemarks" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;Placemark&gt;&lt;TimeStamp&gt;.*&lt;/TimeStamp&gt;&lt;/Placemark&gt;"
				replace=""
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Fill name, remove other" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;name&gt;.*&lt;description&gt;(.*)&lt;/description&gt;(&lt;Placemark&gt;)"
				replace="\2&lt;name&gt;\1&lt;/name&gt;"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove last name/description" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;/Placemark&gt;)&lt;name&gt;.*&lt;/description&gt;"
				replace="\1"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove double names" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;name&gt;(.*)&lt;/name&gt;&lt;name&gt;.*&lt;/name&gt;"
				replace="&lt;name&gt;\1&lt;/name&gt;"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove strange underlines" />
		<replaceregexp
				encoding="utf-8"
				match="&lt;u&gt;(.*)&lt;/u&gt;"
				replace="\1"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<antcall target="timeconversion" />

		<echo message="Reformat description" />
		<replaceregexp
				encoding="utf-8"
				match="(Duration\: )(\d{1}h\d{2}'\d{2}&quot;)"
				replace="\10\2"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="(Duration\: )(\d{2})h(\d{2})'(\d{2})&quot;"
				replace="\1\2:\3:\4"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="(&lt;description&gt;&lt;!\[CDATA\[)Start\: (\d{2}\.\d{2}\.\d{4}).*?(\d{2}\:\d{2})\:\d{2} .*?End\: \d{2}\.\d{2}\.\d{4}.*?(\d{2}\:\d{2})\:\d{2} .*?Duration\: (\d{2}\:\d{2})\:\d{2}.*?Length\: (.*?)km.*?Avg Speed\: (.*?)km h.*?Max Speed\: (.*?)km h.*?Low\: (.*?)m.*?High\: (.*?)m.*?Gain\: (.*?)m.*(\]\]&gt;&lt;/description&gt;)"
				replace="\1Am \2 von \3 bis \4 Uhr, Dauer: \5 h, Strecke: \6 km, Ø: \7 km/h, v_max: \8 km/h, Höhenmeter: \11 m von \9 m bis \10 m\12"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Insert TimeStamp" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;/styleUrl&gt;)(&lt;gx:Track&gt;)(&lt;altitudeMode&gt;.*&lt;/altitudeMode&gt;)(&lt;when&gt;.*?&lt;/when&gt;)"
				replace="\1&lt;TimeStamp&gt;\4&lt;/TimeStamp&gt;\2\3"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Remove other TimeStamps" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;altitudeMode&gt;.*&lt;/altitudeMode&gt;)&lt;when&gt;.*&lt;/when&gt;"
				replace="\1"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Fill color (RGBT)" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;color&gt;).{8}(&lt;/color&gt;)"
				replace="\1${kml:color}\2"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<echo message="Reformat color (RGBT -> TBGR)" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;color&gt;)(..)(..)(..)(..)(&lt;/color&gt;)"
				replace="\1\5\4\3\2\6"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Fill width" />
		<replaceregexp
				encoding="utf-8"
				match="(&lt;width&gt;).(&lt;/width&gt;)"
				replace="\1${kml:width}\2"
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

		<echo message="Tidy XML" />
		<apply executable="xmllint" parallel="false" force="true" dir="${kml:path}" relative="true">
			<arg value="--format" />
			<arg value="--output" />
			<targetfile />
			<srcfile />
			<fileset dir="${kml:path}" includes="*.kml" />
			<mapper type="identity" />
		</apply>

	</target>

	<target name="timeconversion" depends="clearLog">

		<echo message="Reformat time" />

		<replaceregexp
				encoding="utf-8"
				match="(\d{1}\:\d{2}\:\d{2}) vorm\. "
				replace="0\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="(\d{2}\:\d{2}\:\d{2}) vorm\. "
				replace="\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="1(\:\d{2}\:\d{2}) nachm\. "
				replace="13\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="2(\:\d{2}\:\d{2}) nachm\. "
				replace="14\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="3(\:\d{2}\:\d{2}) nachm\. "
				replace="15\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="4(\:\d{2}\:\d{2}) nachm\. "
				replace="16\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="5(\:\d{2}\:\d{2}) nachm\. "
				replace="17\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="6(\:\d{2}\:\d{2}) nachm\. "
				replace="18\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="7(\:\d{2}\:\d{2}) nachm\. "
				replace="19\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="8(\:\d{2}\:\d{2}) nachm\. "
				replace="20\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="9(\:\d{2}\:\d{2}) nachm\. "
				replace="21\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="10(\:\d{2}\:\d{2}) nachm\. "
				replace="22\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="11(\:\d{2}\:\d{2}) nachm\. "
				replace="23\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>
		<replaceregexp
				encoding="utf-8"
				match="12(\:\d{2}\:\d{2}) nachm\. "
				replace="00\1 "
				flags="g">

			<fileset dir="${kml:path}" includes="*.kml" />

		</replaceregexp>

	</target>

</project>

<!-- EOF -->
Kategorien
Programmierung

xjc: fehlender Modul-Export

xjc 2.3.2 liefert mir eine Exception wegen eines fehlenden Modul-Exports von com.sun.tools.xjc zu com.sun.xml.bind.

Der Fehler ist im Git gefixt, seitdem gab es noch kein neues Release, also hab ich xjc selbst kompiliert. Vorgehen siehe https://ekkart.de/computer/programmierung/java/jaxb/

Vielleicht nützt das ja jemandem bis zum nächsten Release.

Kategorien
Computer Programmierung

Java import-Fehler von QName

Die Umstellung von Java 8 auf Java 9+ ist ja sowieso schon mühselig, wenn man aber ganz exotische Fehler bekommt…

Bei mir ging folgender Import nicht:

import javax.xml.namespace.QName;

Erstaunlicherweise ist QName eine Java-Standardklasse, sie wurde per Autocomplete auch gefunden, dann aber als not found gekennzeichnet.

Der Fehler war, dass im Classpath die Maven-Abhängigkeiten vor der JRE-Library standen. Wenn man das ändert (in eclipse bei Java Build Path und Order/Export vertauschen, auch wenn die nicht angekreuzt sind!), dann funktioniert es wieder.

WTF?

Offensichtlich schießt eine Maven-Abhängigkeit da quer, die aber erst beim Kompilieren zuschlägt. Wie soll man da drauf kommen.

Zum Glück gibt es StackOverflow, aber auch dort nur eine Antwort zum Thema. Kann sogar sein, dass das erst ab Java 10 oder 11 auftritt, so genau hab ich nicht geforscht.

Also nochmal: WTF?

Kategorien
Computer

eclipse-Problem, maven funzt

Weiteres Problem gelöst: meine Quellen kompilierten mit maven aus eclipse heraus (run as -> maven install) in eclipse selbst gab es jedoch Fehler wegen nicht geladener Abhängigkeiten.

Geholfen hat: maven -> Update Project

Kategorien
Computer

eclipse-Probleme beim Kompilieren von Tests

Ich hatte das Problem, das nach einem Update von eclipse meine Testklassen nicht mehr kompilierten, da die JUnit-5-Testklassen (per maven eingebunden)  nicht gefunden wurden.

Ich vermutete erst JUnit als Übeltäter, eine Kompilierung über maven funktionierte jedoch.

Stack Overflow ist mein Freund: in eclipse wurde ein neues Attribut eingeführt, das die Testklassen identifiziert, vorher ging das automatisch, wenn man sie in ein Verzeichnis „test“ gelegt hat. Da die Klassen nun nicht mehr als Testklassen gekennzeichnet waren, wurden auch die JUnit-Klassen nicht geladen, da diese per maven im scope „test“ definiert wurden.

Eigentlich logisch, wenn man es weiß.

Also in eclipse die Projekteigenschaften aufrufen, dort im Java Build Path in den Sources bei Contains Test Sources „Yes“ durch Doppelklick einstellen.

Kompiliert immer noch nicht, aber ein Fehler weniger ist ein Fehler weniger…

Kategorien
Computer

Java 10 und JavaFX, Oracle Java und OpenJDK

Es ist manchmal ein Kreuz mit Java.

Zunächst muss man sich unter Linux (wahrscheinlich auch unter Windows, wer weiß), entscheiden, ob man mit Oracle Java oder OpenJDK entwickelt.

Das ist solange kein Unterschied, bis man JavaFX einsetzen will, das XML-basierte GUI-Elemente zur Verfügung stellt. JavaFX ist bei Oracle bei Java 8 und 9 dabei, bei OpenJDK muss man das extra installieren oder sich sein OpenJDK selbst compilieren. Ich will ja eigentlich nur Java programmieren, also Oracle Java genommen (damals Java 8), weil das JavaFX enthält und unter Linux funktioniert.

Als Java 9 rauskam habe ich das erst mal ignoriert, weil es mir von der Sprache her außer Modulen nicht viel bringt und in die wollte ich mich erst einarbeiten, wenn Version 1 meines Programms steht. Außerdem startet Java 9 mit Java 8 compilierte jar-Dateien nicht, ich hätte mich also tatsächlich einarbeiten müssen, unabhängig davon, ob ich Module nutze oder nicht – war mir zu mühselig.

Jetzt ist ein Fehler im Double-Spinner aufgetreten (es wird eine Exception geworfen, wenn ein leerer Text eingegeben wird). Dieser Fehler ist ab Java 9 behoben.

Im Gegensatz zu früher kann man seit Java 9 OpenJDK tatsächlich gleichwertig zu Oracle Java benutzen, da ist Oracle wohl etwas offener geworden. Außerdem soll alle halbe Jahre eine neue Java-Version rauskommen.

Daher habe ich mit Vorfreude OpenJDK 11 installiert (das bis Oktober ein Java 10 installiert – fragt nicht) und sofort gemerkt, dass das Programm nicht kompiliert, weil die JavaFX-Klassen fehlen. Wie jetzt – ich dachte, OpenJDK ist gleichwertig?

Das ist so, dass ab Java 10 sowohl bei Oracle als auch bei OpenJDK JavaFX nicht mehr enthalten ist. Bei beiden Distributionen muss man JavaFX separat installieren. Das soll wohl die Entwicklung separieren und dynamischer gestalten, gerade im Hinblick auf die mit Java 9 eingeführte Modularisierung. Langfristig soll JavaFX auch auch als Maven- oder Gradle-Dependency zur Verfügung stehen.

Steht es aber nicht, ich müsste also entweder ein OpenJDK selbst compilieren (glaubt mir, die Anleitungen sind nicht vertrauenserweckend) oder auf Oracle Java 8 oder 9 zurückgehen. Außerdem ist unklar, ob JavaFX bleibt oder mit Oracles Entscheidung endet. Beides sind realistische Optionen, ich hoffe natürlich langfristig auf eine Dependency.

Konkret werde ich auf Oracle Java 8 zurückgehen, einen üblen Hack für den Spinner-Bug implementieren und Version 1 fertigstellen. Immerhin nutze ich so eine Umgebung, die ich beherrsche.

Dann werde ich mir die Modularisierung ansehen und schauen, inwieweit ich beim dann vorhandenen Java 11 schmerzfrei JavaFX integrieren kann.

Lernzeit dafür: zwei Tage und ein Komplettupdate meines Rechners.

Ich will doch nur programmieren…

Kategorien
Computer

Java: Instanzen von Generics

Ab und an habe ich das Problem, bei Klassen mit Generics eine Instanz des Generics erstellen zu wollen. Das funktioniert leider nicht direkt:

T newObject = new T();

Also muss man um das Problem herumprogrammieren.

Üblicherweise wird die Übergabe einer Klasse empfohlen, die für die Instanziierung genutzt wird. Die Lösung finde ich häßlich, unschön und unelegant.

Java 8 bietet mit der Einführung von Lambdas und entsprechenden Hilfsklassen eine elegantere Möglichkeit, die ich derzeit bevorzuge.

Ich habe das Problem und die Lösungen auf ekkart.de beschrieben: http://www.ekkart.de/computer/programmierung/java/generics.html

Hier die von mir präferierte Lösung:

public class GenericClass<T extends Object> {

  private Supplier<T> instanceCall = null;

  public void init(final Supplier<T> theInstanceCall) {
    instanceCall = theInstanceCall;
  }

  public T getInstance() {
    return instanceCall.get();
  }

}

public class ObjectFactory {
  public Integer createInteger() {
    return new Integer();
  }
}

public class Test {

  public void testGenerics() {
    GenericClass<Integer> testObject = new GenericClass<>();
    ObjectFactory factory = new ObjectFactory();
    testObject.init(factory::createInteger);
    Integer a = testObject.getInstance();
  }

}