From bdfcda54d7fbcec0b3680b1fcf60c0f0c83e68a5 Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@control.lth.se>
Date: Mon, 11 Mar 2019 12:30:18 +0100
Subject: [PATCH] Add java support

---
 Makefile                                      |   5 +
 adaptors/java/Makefile                        |   5 +
 adaptors/java/README                          |   1 +
 adaptors/java/src/Makefile                    |  66 +++
 adaptors/java/src/getProperty_os_arch         |  14 +
 .../realtime/AlreadyAllocatedException.java   |  30 ++
 .../src/se/lth/control/realtime/AnalogIn.java |  42 ++
 .../se/lth/control/realtime/AnalogOut.java    |  46 ++
 .../se/lth/control/realtime/DigitalIn.java    |  44 ++
 .../se/lth/control/realtime/DigitalOut.java   |  43 ++
 .../se/lth/control/realtime/EncoderIn.java    |  44 ++
 .../se/lth/control/realtime/IOChannel.java    |  88 ++++
 .../control/realtime/IOChannelException.java  |  32 ++
 .../lth/control/realtime/moberg/Moberg.java   |  51 +++
 .../MobergConfigurationFileNotFoundError.java |  31 ++
 .../MobergDeviceDoesNotExistException.java    |  31 ++
 .../moberg/MobergDeviceReadException.java     |  31 ++
 .../moberg/MobergDeviceWriteException.java    |  31 ++
 .../realtime/moberg/MobergException.java      |  32 ++
 .../moberg/MobergNotOpenException.java        |  12 +
 .../moberg/MobergRangeNotFoundException.java  |  12 +
 .../se_lth_control_realtime_moberg_Moberg.c   | 413 ++++++++++++++++++
 adaptors/matlab/Makefile                      |   2 +
 moberg.spec.template                          |  12 +-
 24 files changed, 1116 insertions(+), 2 deletions(-)
 create mode 100644 adaptors/java/Makefile
 create mode 100644 adaptors/java/README
 create mode 100644 adaptors/java/src/Makefile
 create mode 100755 adaptors/java/src/getProperty_os_arch
 create mode 100644 adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/AnalogIn.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/AnalogOut.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/DigitalIn.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/DigitalOut.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/EncoderIn.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/IOChannel.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/IOChannelException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java
 create mode 100644 adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java
 create mode 100644 adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c

diff --git a/Makefile b/Makefile
index 97c9151..bca7042 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,10 @@ build/lib/%.o:	%.c Makefile | build/lib
 $(ADAPTORS) $(PLUGINS): 
 	$(MAKE) -C $@
 
+.PHONY: $(PLUGINS) $(ADAPTORS)
+$(ADAPTORS) $(PLUGINS): 
+	$(MAKE) -C $@
+
 .PHONY: TAR
 TAR:
 	git archive \
@@ -60,6 +64,7 @@ clean:
 	rm -f moberg-*.spec
 	rm -f moberg-*.tar.gz
 	make -C test clean
+	for d in $(ADAPTORS) ; do make -C $$d clean ; done
 
 build/libmoberg.so: build/lib/moberg.o
 build/libmoberg.so: build/lib/moberg_config.o
diff --git a/adaptors/java/Makefile b/adaptors/java/Makefile
new file mode 100644
index 0000000..1687218
--- /dev/null
+++ b/adaptors/java/Makefile
@@ -0,0 +1,5 @@
+all:
+	make -f src/Makefile
+
+clean:
+	make -f src/Makefile clean
diff --git a/adaptors/java/README b/adaptors/java/README
new file mode 100644
index 0000000..8dbdf8d
--- /dev/null
+++ b/adaptors/java/README
@@ -0,0 +1 @@
+Moberg support for java
\ No newline at end of file
diff --git a/adaptors/java/src/Makefile b/adaptors/java/src/Makefile
new file mode 100644
index 0000000..d82b263
--- /dev/null
+++ b/adaptors/java/src/Makefile
@@ -0,0 +1,66 @@
+VERSION=1.0
+JARNAME=Moberg
+TARFILE=java_se.lth.control.realtime.Moberg-$(VERSION).tar.gz
+JNINAME=se_lth_control_realtime_moberg_Moberg
+
+JAVAH_PATH=$(shell javah -Xbootclasspath/p:build -version >/dev/null 2>&1 && \
+	echo -Xbootclasspath/p:build || echo -bootclasspath build)
+JNI_INCLUDE=/usr/lib/jvm/java/include/
+CC=gcc
+CC_JNI_FLAGS=-Wall -Werror -shared -fPIC \
+	     -I$(JNI_INCLUDE) -I$(JNI_INCLUDE)/linux -Ibuild \
+	     -lmoberg
+JAVADIR=/tmp
+INSTALL_DIR=$(JAVADIR)/$(JARNAME)
+JAR_DIR=$(INSTALL_DIR)/jre/lib/ext/
+SO_DIR=$(INSTALL_DIR)/jre/lib/$(shell /bin/sh ./src/getProperty_os_arch)
+SRCFILES=README \
+	 src/Makefile \
+	 src/getProperty_os_arch \
+	 $(JNINAME:%=src/%.c) \
+	 $(JAVAFILES:%=src/%)
+JAVAFILES=$(shell find src -name '*.java' | sed -e 's:^src/::g')
+
+all: JAVAC build/$(JARNAME).jar $(JNINAME:%=build/lib%.so)
+
+install: install_src install_jar install_so
+
+install_src:
+	for f in $(SRCFILES) ; do install -D $$f $(INSTALL_DIR)/$$f ; done
+
+install_jar: build/$(JARNAME).jar
+	install -d $(JAR_DIR)
+	install build/$(JARNAME).jar $(JAR_DIR)
+
+install_so: $(JNINAME:%=install_%.so)
+
+install_%.so: build/lib%.so
+	install -d $(SO_DIR)
+	install $< $(SO_DIR)
+
+JAVAC:	$(JAVAFILES:%.java=build/%.class)
+
+build/%.class:	src/%.java
+# Recompile all javafiles at once (do JIT compilation once)
+	mkdir -p build	
+	cd src ; javac -d ../build -target 1.5 -source 1.5 $(JAVAFILES)	
+
+build/$(JARNAME).jar: JAVAC
+	jar -cf $@ \
+	    -C build se \
+	     $(SRCFILES)
+
+build/%.h: $(shell echo $(JNINAME:%=build/%.class) | sed -e 's:_:/:g')
+# Too many dependencies, base the ones we need on $* (matches % above)	
+	javah -o $@ -force $(JAVAH_PATH) $(shell echo $* | sed -e s/_/./g) 
+
+build/lib%.so: $(JNINAME:%=build/%.h) $(JNINAME:%=src/%.c)
+# Too many dependencies, base the ones we need on $* (matches % above)	
+	$(CC) -o $@ $(CC_JNI_FLAGS) -I. src/$*.c
+
+clean:
+	rm -rf build *~
+
+TAR:
+	mkdir -p build
+	tar -C .. -cvzf build/$(TARFILE) $(SRCFILES:%=$(shell basename $$(pwd))/%)
diff --git a/adaptors/java/src/getProperty_os_arch b/adaptors/java/src/getProperty_os_arch
new file mode 100755
index 0000000..a178e0f
--- /dev/null
+++ b/adaptors/java/src/getProperty_os_arch
@@ -0,0 +1,14 @@
+#!/usr/bin/sh
+
+BASENAME=`basename $0`
+cat > ${BASENAME}.java << EOF
+public class ${BASENAME} {
+  
+  public static void main(String[] args) {
+     System.out.println(System.getProperty("os.arch"));
+  }
+}
+EOF
+javac ${BASENAME}.java
+java ${BASENAME}
+rm ${BASENAME}.java ${BASENAME}.class
diff --git a/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java b/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java
new file mode 100644
index 0000000..91e8253
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AlreadyAllocatedException.java
@@ -0,0 +1,30 @@
+/**
+ * se.lth.control.realtime.moberg.AlreadyAllocatedException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+public class AlreadyAllocatedException extends IOChannelException {
+
+  private static final long serialVersionUID = 1L;
+
+  public AlreadyAllocatedException(String kind, int index) {
+    super(kind, index, "is already allocated");
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/AnalogIn.java b/adaptors/java/src/se/lth/control/realtime/AnalogIn.java
new file mode 100644
index 0000000..3fc715f
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AnalogIn.java
@@ -0,0 +1,42 @@
+/**
+ * se.lth.control.realtime.AnalogIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class AnalogIn extends IOChannel {
+
+  public AnalogIn(final int index) throws IOChannelException {
+    super(index);
+  }
+
+  protected void open() throws IOChannelException {
+    Moberg.analogInOpen(index);
+  }
+
+  protected void close() throws IOChannelException {
+    Moberg.analogInClose(index);
+  }
+
+  public double get() throws IOChannelException {
+    return Moberg.analogIn(index);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/AnalogOut.java b/adaptors/java/src/se/lth/control/realtime/AnalogOut.java
new file mode 100644
index 0000000..4310079
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/AnalogOut.java
@@ -0,0 +1,46 @@
+/**
+ * se.lth.control.realtime.AnalogOut.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+
+public class AnalogOut extends IOChannel {
+
+  public AnalogOut(int index) throws IOChannelException {
+    super(index);
+  }
+
+  public void open() throws IOChannelException {
+    Moberg.analogOutOpen(index);
+  }
+
+  public void close() throws IOChannelException {
+    Moberg.analogOutClose(index);
+  }
+
+  public void set(double value) throws IOChannelException {
+    Moberg.analogOut(index, value);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/DigitalIn.java b/adaptors/java/src/se/lth/control/realtime/DigitalIn.java
new file mode 100644
index 0000000..e34e28b
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/DigitalIn.java
@@ -0,0 +1,44 @@
+/**
+ * se.lth.control.realtime.DigitalIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class DigitalIn extends IOChannel {
+
+  public DigitalIn(int index) throws IOChannelException {
+    super(index);
+  }
+
+  public void open() throws IOChannelException {
+    Moberg.digitalInOpen(index);
+  }
+
+  public void close() throws IOChannelException {
+    Moberg.digitalInClose(index);
+  }
+
+  public boolean get() throws IOChannelException {
+    return Moberg.digitalIn(index);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/DigitalOut.java b/adaptors/java/src/se/lth/control/realtime/DigitalOut.java
new file mode 100644
index 0000000..a14f189
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/DigitalOut.java
@@ -0,0 +1,43 @@
+/**
+ * se.lth.control.realtime.DigitalOut.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class DigitalOut extends IOChannel {
+
+  public DigitalOut(int index) throws IOChannelException {
+    super(index);
+  }
+   
+  public void open() throws IOChannelException {
+    Moberg.digitalOutOpen(index);
+  }
+  public void close() throws IOChannelException {
+    Moberg.digitalOutClose(index);
+  }
+
+  public void set(boolean value) throws IOChannelException {
+    Moberg.digitalOut(index, value);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/EncoderIn.java b/adaptors/java/src/se/lth/control/realtime/EncoderIn.java
new file mode 100644
index 0000000..d1f919d
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/EncoderIn.java
@@ -0,0 +1,44 @@
+/**
+ * se.lth.control.realtime.EncoderIn.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.BitSet;
+
+import se.lth.control.realtime.moberg.Moberg;
+
+public class EncoderIn extends IOChannel {
+  
+  public EncoderIn(int index) throws IOChannelException {
+    super(index);
+  }
+
+  protected void open() throws IOChannelException {
+     Moberg.encoderInOpen(index);
+  }
+
+  protected void close() throws IOChannelException {
+    Moberg.encoderInClose(index);
+  }
+  
+  public long get() throws IOChannelException {
+    return Moberg.encoderIn(index);
+  }
+    
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/IOChannel.java b/adaptors/java/src/se/lth/control/realtime/IOChannel.java
new file mode 100644
index 0000000..6f83f0c
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/IOChannel.java
@@ -0,0 +1,88 @@
+/**
+ * se.lth.control.realtime.moberg.IOChannel.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ * Copyright (C) 2014 Alfred Theorin <alfred.theorin@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.util.HashMap;
+import java.util.BitSet;
+
+public abstract class IOChannel {
+
+  private static HashMap allocations = new HashMap();
+
+  public final int index;
+  private BitSet allocation;
+  private boolean isAllocated = false;
+
+  public IOChannel(int index) throws IOChannelException {
+    this.index = index;
+    open();
+    allocate();
+  }
+
+  private BitSet getAllocation() {
+    BitSet result = allocation;
+    if (result == null) {
+      result = (BitSet)allocations.get(this.getClass().getName());
+      if (result == null) {
+	result = new BitSet();
+	allocations.put(this.getClass().getName(), result);
+      }
+    }
+    return result;
+  }
+
+  private synchronized void allocate() throws IOChannelException {
+    if (getAllocation().get(index)) {
+      // Try to free unreferenced channels 
+      System.gc();
+      System.runFinalization();
+    }
+    if (getAllocation().get(index)) {
+      try {
+	close();
+      } catch (IOChannelException e) {
+      }
+      throw new AlreadyAllocatedException(this.getClass().getName(), index);
+    }
+    getAllocation().set(index);
+    isAllocated = true;
+  }
+
+  private synchronized void deallocate() {
+    if (isAllocated) {
+      getAllocation().clear(index);
+      try {
+        close();
+      } catch (IOChannelException e) {
+      }
+      isAllocated = false;
+    }
+  }
+
+
+  protected abstract void open() throws IOChannelException;
+  protected abstract void close() throws IOChannelException;
+
+  protected void finalize() throws IOChannelException {
+    deallocate();
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/IOChannelException.java b/adaptors/java/src/se/lth/control/realtime/IOChannelException.java
new file mode 100644
index 0000000..69ff096
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/IOChannelException.java
@@ -0,0 +1,32 @@
+/**
+ * se.lth.control.realtime.IOChannelException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime;
+
+import java.io.IOException;
+
+public class IOChannelException extends IOException {
+
+  private static final long serialVersionUID = 1L;
+
+  public IOChannelException(String kind, int index, String s) {
+    super(kind + " channel #" + index + " " + s);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java b/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java
new file mode 100644
index 0000000..2a79c03
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/Moberg.java
@@ -0,0 +1,51 @@
+/**
+ * se.lth.control.realtime.moberg.Moberg.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+import java.io.File;
+
+public class Moberg {
+  public static native void analogInOpen(int index) throws MobergException;
+  public static native void analogInClose(int index) throws MobergException;
+  public static native double analogIn(int index) throws MobergException;
+
+  public static native void analogOutOpen(int index) throws MobergException;
+  public static native void analogOutClose(int index) throws MobergException;
+  public static native void analogOut(int index, double value) throws MobergException;
+
+  public static native void digitalInOpen(int index) throws MobergException;
+  public static native void digitalInClose(int index) throws MobergException;
+  public static native boolean digitalIn(int index) throws MobergException;
+
+  public static native void digitalOutOpen(int index) throws MobergException;
+  public static native void digitalOutClose(int index) throws MobergException;
+  public static native void digitalOut(int index, boolean value) throws MobergException;
+
+  public static native void encoderInOpen(int index) throws MobergException;
+  public static native void encoderInClose(int index) throws MobergException;
+  public static native long encoderIn(int index) throws MobergException;
+
+  private static native void Init();
+
+  static  {
+    System.loadLibrary("se_lth_control_realtime_moberg_Moberg");
+    Init();
+  }
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java
new file mode 100644
index 0000000..7daa0fe
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergConfigurationFileNotFoundError.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergConfigurationFileNotFoundError extends Error {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergConfigurationFileNotFoundError(int index) {
+    super("Moberg configuration file '/etc/moberg.conf' was not found");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java
new file mode 100644
index 0000000..ffc876f
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceDoesNotExistException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceDoesNotExistException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceDoesNotExistException(int index) {
+    super(index, "does not exist");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java
new file mode 100644
index 0000000..2b9b056
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceReadException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceReadException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceReadException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceReadException(int index) {
+    super(index, "read failed");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java
new file mode 100644
index 0000000..fa24bd1
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergDeviceWriteException.java
@@ -0,0 +1,31 @@
+/**
+ * se.lth.control.realtime.moberg.MobergDeviceWriteException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+
+public class MobergDeviceWriteException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergDeviceWriteException(int index) {
+    super(index, "write failed");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java
new file mode 100644
index 0000000..015acd0
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergException.java
@@ -0,0 +1,32 @@
+/**
+ * se.lth.control.realtime.moberg.MobergException.java
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.lth.control.realtime.moberg;
+
+import se.lth.control.realtime.IOChannelException;
+
+public class MobergException extends IOChannelException {
+
+  private static final long serialVersionUID = 1L;
+
+  public MobergException(int index, String reason) {
+    super("Moberg", index, reason);
+  }
+
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java
new file mode 100644
index 0000000..2e23889
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergNotOpenException.java
@@ -0,0 +1,12 @@
+package se.lth.control.realtime.moberg;
+
+
+public class MobergNotOpenException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergNotOpenException(int index) {
+    super(index, "is not open");
+  }
+  
+}
diff --git a/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java b/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java
new file mode 100644
index 0000000..fb4d8d0
--- /dev/null
+++ b/adaptors/java/src/se/lth/control/realtime/moberg/MobergRangeNotFoundException.java
@@ -0,0 +1,12 @@
+package se.lth.control.realtime.moberg;
+
+
+public class MobergRangeNotFoundException extends MobergException {
+  
+  private static final long serialVersionUID = 1L;
+  
+  public MobergRangeNotFoundException(int index) {
+    super(index, "has no range information");
+  }
+  
+}
diff --git a/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c b/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c
new file mode 100644
index 0000000..9363df3
--- /dev/null
+++ b/adaptors/java/src/se_lth_control_realtime_moberg_Moberg.c
@@ -0,0 +1,413 @@
+/**
+ * se_lth_control_realtime_moberg_Moberg.c
+ *
+ * Copyright (C) 2005-2019  Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <jni.h>
+#include "se_lth_control_realtime_moberg_Moberg.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <moberg.h>
+
+static void throwMoberg(JNIEnv *env, int chan, char *exceptionName)
+{
+  jclass exceptionClass = 0;
+  jmethodID exceptionClassInit = 0;
+  jobject exception = 0;
+  jint result = -1;
+
+  exceptionClass = 
+    (*env)->FindClass(env, exceptionName);
+  if (exceptionClass) {
+     exceptionClassInit = (*env)->GetMethodID(env, exceptionClass,
+					      "<init>", "(I)V");
+  }
+  if (exceptionClassInit) {
+    exception = (*env)->NewObject(env, exceptionClass, exceptionClassInit, 
+				  chan);
+  }
+  if (exception) {
+    result = (*env)->Throw(env, exception);
+  }
+  if (result != 0) {
+    fprintf(stderr, "%s: %d\n", exceptionName, chan);
+  }
+}
+
+static void throwMobergNotOpenException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergNotOpenException");		      
+}
+
+static void throwMobergDeviceDoesNotExistException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceDoesNotExistException");		      
+}
+
+#if 0
+static void throwMobergDeviceReadException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceReadException");		      
+}
+
+static void throwMobergDeviceWriteException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergDeviceWriteException");		      
+}
+
+static void throwMobergRangeNotFoundException(JNIEnv *env, int chan)
+{
+  throwMoberg(env, chan, "se/lth/control/realtime/moberg/MobergRangeNotFoundException");		      
+}
+
+static void throwMobergConfigurationFileNotFoundError(JNIEnv *env)
+{
+  throwMoberg(env, 0, "se/lth/control/realtime/moberg/MobergConfigurationFileNotFoundError");		      
+}
+#endif
+
+static struct list {
+  int capacity;
+  struct channel {
+    int count;
+    union {
+      struct moberg_analog_in analog_in;
+      struct moberg_analog_out analog_out;
+      struct moberg_digital_in digital_in;
+      struct moberg_digital_out digital_out;
+      struct moberg_encoder_in encoder_in;
+    };
+  } *channel;
+} analog_in = { .capacity=0, .channel=NULL },
+  analog_out = { .capacity=0, .channel=NULL },
+  digital_in = { .capacity=0, .channel=NULL },
+  digital_out = { .capacity=0, .channel=NULL },
+  encoder_in = { .capacity=0, .channel=NULL };
+
+struct {
+  int count;
+  struct moberg *moberg;
+} g_moberg = { 0, NULL };
+
+static int up()
+{
+  if (g_moberg.count <= 0) {
+    g_moberg.moberg = moberg_new(NULL);
+  }
+  g_moberg.count++;
+  return 0;
+}
+
+static int down()
+{
+  g_moberg.count--;
+  if (g_moberg.count <= 0) {
+    moberg_free(g_moberg.moberg);
+    g_moberg.moberg = NULL;
+  }
+  return 0;
+}
+
+static struct channel *channel_get(struct list *list, int index)
+{
+  if (index < list->capacity && list->channel[index].count > 0) {
+    return &list->channel[index];
+  }
+  return NULL;
+}
+
+static int channel_set(struct list *list, int index, struct channel channel)
+{
+  if (list->capacity < index) {
+    int capacity = index + 1;
+    void *new = realloc(list->channel, capacity * sizeof(*list->channel));
+    if (new) {
+      void *p = new + list->capacity * sizeof(*list->channel);
+      memset(p, 0, (capacity - list->capacity) * sizeof(*list->channel));
+      list->channel = new;
+      list->capacity = capacity;
+    }
+  }
+  if (0 <= index && index < list->capacity) {
+    list->channel[index] = channel;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+static int channel_up(struct list *list, int index)
+{
+  struct channel *channel = channel_get(list, index);
+  if (channel) {
+    up();
+    channel->count++;
+    return 1;
+  }
+  return 0;
+}
+
+static struct channel *channel_down(struct list *list, int index)
+{
+  struct channel *channel = channel_get(list, index);
+  if (channel) {
+    down();
+    channel->count--;
+    return channel;
+  }
+  return NULL;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&analog_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_analog_in_open(g_moberg.moberg, index, &channel.analog_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&analog_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&analog_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_analog_in_close(g_moberg.moberg, index, channel->analog_in);
+  }
+}
+
+
+JNIEXPORT jdouble JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  double result = 0.0;
+
+  struct channel *channel = channel_get(&analog_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->analog_in.read(channel->analog_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOutOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&analog_out, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_analog_out_open(g_moberg.moberg, index, &channel.analog_out)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&analog_out, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOutClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&analog_out, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_analog_out_close(g_moberg.moberg, index, channel->analog_out);
+  }
+}
+
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_analogOut(
+  JNIEnv *env, jobject obj, jint index, jdouble value
+) 
+{
+  struct channel *channel = channel_get(&analog_out, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->analog_out.write(channel->analog_out.context, value);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&digital_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_digital_in_open(g_moberg.moberg, index, &channel.digital_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&digital_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&digital_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_digital_in_close(g_moberg.moberg, index, channel->digital_in);
+  }
+}
+
+JNIEXPORT jboolean JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  int result = 0;
+
+  struct channel *channel = channel_get(&digital_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->digital_in.read(channel->digital_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOutOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&digital_out, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_digital_out_open(g_moberg.moberg, index, &channel.digital_out)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&digital_out, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOutClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&digital_out, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_digital_out_close(g_moberg.moberg, index, channel->digital_out);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_digitalOut(
+  JNIEnv *env, jobject obj, jint index, jboolean value
+) 
+{
+  struct channel *channel = channel_get(&digital_out, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->digital_out.write(channel->digital_out.context, value);
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderInOpen(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  if (! channel_up(&encoder_in, index)) {
+    struct channel channel;
+    up();
+    if (! moberg_encoder_in_open(g_moberg.moberg, index, &channel.encoder_in)) {
+      down();
+      throwMobergDeviceDoesNotExistException(env, index);      
+    } else {
+      channel.count = 1;
+      channel_set(&encoder_in, index, channel);
+    }
+  }
+}
+
+JNIEXPORT void JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderInClose(
+  JNIEnv *env, jclass obj, jint index
+)
+{
+  struct channel *channel = channel_down(&encoder_in, index);
+  if (! channel) {
+    throwMobergDeviceDoesNotExistException(env, index);
+  } else if (channel->count == 0) {
+    moberg_encoder_in_close(g_moberg.moberg, index, channel->encoder_in);
+  }
+}
+
+JNIEXPORT jlong JNICALL 
+Java_se_lth_control_realtime_moberg_Moberg_encoderIn(
+  JNIEnv *env, jobject obj, jint index
+) 
+{
+  long result = 0;
+
+  struct channel *channel = channel_get(&encoder_in, index);
+  if (! channel) {
+    throwMobergNotOpenException(env, index);
+  } else {
+    channel->encoder_in.read(channel->encoder_in.context, &result);
+  }
+  return result;
+}
+
+JNIEXPORT void JNICALL
+Java_se_lth_control_realtime_moberg_Moberg_Init(
+  JNIEnv *env, jobject obj
+)
+{
+}
diff --git a/adaptors/matlab/Makefile b/adaptors/matlab/Makefile
index 561bfd6..d59df21 100644
--- a/adaptors/matlab/Makefile
+++ b/adaptors/matlab/Makefile
@@ -3,6 +3,8 @@ CCFLAGS+=-Wall -Werror -O3 -I. -I../.. -g
 
 all:	$(LIBRARIES:%=../../build/%)
 
+clean:
+
 ../../build/libmoberg4simulink.so: moberg4simulink.c Makefile
 	$(CC) -o $@ $(CCFLAGS) -L../../build -shared -fPIC -lmoberg $<
 
diff --git a/moberg.spec.template b/moberg.spec.template
index 36e5438..504f1ae 100644
--- a/moberg.spec.template
+++ b/moberg.spec.template
@@ -9,6 +9,7 @@ BuildRequires:  gcc
 BuildRequires:  comedilib-devel
 BuildRequires:  valgrind
 BuildRequires:  libxdg-basedir-devel
+BuildRequires:  java-devel
 
 %description
 Shared library for abstracting physical process I/O (analog, digital
@@ -30,12 +31,19 @@ Requires: %{name} = %{version}-%{release}
 Development files for %{name}
 
 %package matlab
-Summary: Matlab development files for %{name}
+Summary: Matlab support files for %{name}
 Requires: %{name} = %{version}-%{release}
 Requires: %{name}-devel = %{version}-%{release}
 
 %description matlab
-Matlab development files for %{name}
+Matlab support files for %{name}
+
+%package java
+Summary: Java support files for %{name}
+Requires: %{name} = %{version}-%{release}
+
+%description java
+Java support files for %{name}
 
 
 %prep
-- 
GitLab