]> gitweb.hamatoma.de Git - jpinet/commitdiff
new: Expression, PWM output
authorhama <hama@siduction.net>
Sun, 24 Jul 2016 11:23:45 +0000 (13:23 +0200)
committerhama <hama@siduction.net>
Sun, 24 Jul 2016 11:25:09 +0000 (13:25 +0200)
29 files changed:
pom.xml
src/main/java/de/republib/expr/DataType.java [new file with mode: 0644]
src/main/java/de/republib/expr/Expression.java [new file with mode: 0644]
src/main/java/de/republib/expr/OpCode.java [new file with mode: 0644]
src/main/java/de/republib/expr/ParserException.java [new file with mode: 0644]
src/main/java/de/republib/expr/Scanner.java [new file with mode: 0644]
src/main/java/de/republib/expr/Token.java [new file with mode: 0644]
src/main/java/de/republib/expr/TokenType.java [new file with mode: 0644]
src/main/java/de/republib/expr/Variable.java [new file with mode: 0644]
src/main/java/de/republib/expr/Variant.java [new file with mode: 0644]
src/main/java/de/republib/expr/VariantException.java [new file with mode: 0644]
src/main/java/de/republib/gui/Channel.java
src/main/java/de/republib/gui/GuiUtils.java [new file with mode: 0644]
src/main/java/de/republib/gui/MultiChannelSheet.java
src/main/java/de/republib/net/TcpClient.java
src/main/java/de/republib/pinet/Function.java [new file with mode: 0644]
src/main/java/de/republib/pinet/GpioClient.java
src/main/java/de/republib/pinet/gui/ControlCenter.java
src/main/java/de/republib/pinet/gui/GPIOSettings.java
src/main/java/de/republib/util/StringUtils.java
src/main/resources/icons/focus16.png [new file with mode: 0644]
src/main/resources/icons/input16.png [new file with mode: 0644]
src/main/resources/icons/output16.png [new file with mode: 0644]
src/main/resources/icons/undef16.png [new file with mode: 0644]
src/test/java/de/republib/expr/ExpressionParserTest.java [new file with mode: 0644]
src/test/java/de/republib/expr/ScannerTest.java [new file with mode: 0644]
src/test/java/de/republib/expr/VariableTest.java [new file with mode: 0644]
src/test/java/de/republib/expr/VariantTest.java [new file with mode: 0644]
src/test/java/de/republib/gui/GuiUtilsTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 20b04d803c31f6de5fe2285965889644cae7528b..ca1e11d9a3034e3d07de12d4c4b20eb852c76335 100644 (file)
--- a/pom.xml
+++ b/pom.xml
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
-  <modelVersion>4.0.0</modelVersion>
+       <modelVersion>4.0.0</modelVersion>
 
-  <groupId>de.republlb</groupId>
-  <artifactId>pinet</artifactId>
-  <packaging>jar</packaging>
-  <version>1.0-SNAPSHOT</version>
+       <groupId>de.republlb</groupId>
+       <artifactId>pinet</artifactId>
+       <packaging>jar</packaging>
+       <version>1.0-SNAPSHOT</version>
 
-  <name>Controlling a Raspberry Pi</name>
-  <url>https://sourcefourge.net</url>
+       <name>Controlling a Raspberry Pi</name>
+       <url>https://sourcefourge.net</url>
 
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-      <logback.version>1.1.7</logback.version>
-      <slf4j.version>1.7.21</slf4j.version>
-  </properties>
+       <properties>
+               <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+               <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+               <logback.version>1.1.7</logback.version>
+               <slf4j.version>1.7.21</slf4j.version>
+       </properties>
 
-  <dependencies>
-    <!-- logging -->
-      <dependency>
-          <groupId>org.slf4j</groupId>
-          <artifactId>slf4j-api</artifactId>
-          <version>${slf4j.version}</version>
-      </dependency>
-      <dependency>
-          <groupId>ch.qos.logback</groupId>
-          <artifactId>logback-classic</artifactId>
-          <version>${logback.version}</version>
-      </dependency>
-      <dependency>
-          <groupId>ch.qos.logback</groupId>
-          <artifactId>logback-core</artifactId>
-          <version>${logback.version}</version>
-      </dependency>
-    <dependency>
-      <groupId>org.testng</groupId>
-      <artifactId>testng</artifactId>
-      <version>6.9.8</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
+       <dependencies>
+               <!-- logging -->
+               <dependency>
+                       <groupId>org.slf4j</groupId>
+                       <artifactId>slf4j-api</artifactId>
+                       <version>${slf4j.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>ch.qos.logback</groupId>
+                       <artifactId>logback-classic</artifactId>
+                       <version>${logback.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>ch.qos.logback</groupId>
+                       <artifactId>logback-core</artifactId>
+                       <version>${logback.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.testng</groupId>
+                       <artifactId>testng</artifactId>
+                       <version>6.9.8</version>
+                       <scope>test</scope>
+               </dependency>
+               <!-- https://mvnrepository.com/artifact/com.miglayout/miglayout-swing -->
+               <dependency>
+                       <groupId>com.miglayout</groupId>
+                       <artifactId>miglayout-swing</artifactId>
+                       <version>4.2</version>
+               </dependency>
 
-  <build>
-    <defaultGoal>install</defaultGoal>
+       </dependencies>
 
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.5.1</version>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-resources-plugin</artifactId>
-        <version>2.6</version>
-        <configuration>
-          <encoding>UTF-8</encoding>
-        </configuration>
-      </plugin>
+       <build>
+               <defaultGoal>install</defaultGoal>
 
-      <plugin>
-      <groupId>org.apache.maven.plugins</groupId>
-      <artifactId>maven-surefire-plugin</artifactId>
-      <version>2.12.4</version>
-      <configuration>
-        <parallel>methods</parallel>
-        <threadCount>4</threadCount>
-      </configuration>
-      </plugin>
-      <!-- Allows the example to be run via 'mvn compile exec:java' -->
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>exec-maven-plugin</artifactId>
-        <version>1.4.0</version>
-        <configuration>
-          <mainClass>de.republlb.MainApp</mainClass>
-          <includePluginDependencies>false</includePluginDependencies>
-        </configuration>
-      </plugin>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <version>3.5.1</version>
+                               <configuration>
+                                       <source>1.7</source>
+                                       <target>1.7</target>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-resources-plugin</artifactId>
+                               <version>2.6</version>
+                               <configuration>
+                                       <encoding>UTF-8</encoding>
+                               </configuration>
+                       </plugin>
 
-    </plugins>
-  </build>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-surefire-plugin</artifactId>
+                               <version>2.12.4</version>
+                               <configuration>
+                                       <parallel>methods</parallel>
+                                       <threadCount>4</threadCount>
+                               </configuration>
+                       </plugin>
+                       <!-- Allows the example to be run via 'mvn compile exec:java' -->
+                       <plugin>
+                               <groupId>org.codehaus.mojo</groupId>
+                               <artifactId>exec-maven-plugin</artifactId>
+                               <version>1.4.0</version>
+                               <configuration>
+                                       <mainClass>de.republlb.MainApp</mainClass>
+                                       <includePluginDependencies>false</includePluginDependencies>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <artifactId>maven-assembly-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <phase>package</phase>
+                                               <goals>
+                                                       <goal>single</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                               <configuration>
+                                       <descriptorRefs>
+                                               <descriptorRef>jar-with-dependencies</descriptorRef>
+                                       </descriptorRefs>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-source-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>attach-sources</id>
+                                               <goals>
+                                                       <goal>jar</goal>
+                                               </goals>
+                                       </execution>
+                               </executions>
+                       </plugin>
+               </plugins>
+       </build>
 
 </project>
diff --git a/src/main/java/de/republib/expr/DataType.java b/src/main/java/de/republib/expr/DataType.java
new file mode 100644 (file)
index 0000000..8ad6904
--- /dev/null
@@ -0,0 +1,14 @@
+package de.republib.expr;
+/**
+ *
+ */
+
+/**
+ * Enumerates the data types which characterizes the storage of a value.
+ *
+ * @author hm
+ *
+ */
+public enum DataType {
+       UNDEF, BOOLEAN, LONG, DOUBLE, STRING, OBJECT, VARIABLE
+}
diff --git a/src/main/java/de/republib/expr/Expression.java b/src/main/java/de/republib/expr/Expression.java
new file mode 100644 (file)
index 0000000..a995173
--- /dev/null
@@ -0,0 +1,178 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+import java.util.HashMap;
+import java.util.Stack;
+
+import de.republib.util.I18N;
+
+/**
+ * Implements a parser for arithmetic expressions (formulas).
+ *
+ * <pre>
+ * Syntax:
+ * <expr> ::= <term> {<operator> <operand}*
+ * <term> ::= '(' <expr> ')' | <constant> | <variable>
+ * <constant> ::= <number>
+ * <operator> ::= '+' | '-' | '*' | '/' | '%' | '**' | '='
+ * </pre>
+ *
+ * @author hm
+ *
+ */
+public class Expression {
+       protected Scanner scanner;
+       protected HashMap<String, Variable> variables = new HashMap<>();
+       protected Stack<Variant> values = new Stack<>();
+       protected Stack<OpCode> operators = new Stack<>();
+
+       /**
+        * Constructor.
+        *
+        * @param text
+        *            the text with the formula
+        */
+       public Expression(String text) {
+               this.scanner = new Scanner(text);
+       }
+
+       /**
+        * Calculates the value of the expression.
+        *
+        * <pre>
+        * <expr> ::= <term> { <operator> <term> }*
+        * </pre>
+        *
+        * @return the value of the expression stored in a token
+        * @throws ParserException
+        * @throws VariantException
+        */
+       public Variant expr() throws ParserException {
+               final Variant rc = term();
+               if (rc == null) {
+                       throw new ParserException(I18N.tr("unexpected end of string"), this.scanner.getLastToken(), false);
+               }
+               Token token;
+               final boolean again = true;
+               while (again && !(token = this.scanner.nextNotSpaceToken()).isType(TokenType.END_OF_STRING)) {
+                       if (!token.isType(TokenType.OP)) {
+                               throw new ParserException(
+                                               String.format(I18N.tr("operator expected (like '+', '-'...). Found: %s"), token.getType()),
+                                               token);
+                       }
+                       final OpCode op = token.getOpCode();
+                       Variant value2;
+                       final Token tokenOp = token.copy();
+                       if (op == OpCode.ASSIGN) {
+                               if (!rc.isType(DataType.VARIABLE)) {
+                                       throw new ParserException(I18N.tr("Assignment misses a rvalue (e.g. a variable) on the left side"),
+                                                       token);
+                               }
+                               value2 = expr();
+                       } else {
+                               value2 = term();
+                       }
+                       try {
+                               rc.binaryOperation(op, value2);
+                       } catch (final VariantException e) {
+                               // transform in a message with position info:
+                               throw new ParserException(e.getMessage(), tokenOp);
+                       }
+               }
+               return rc;
+       }
+
+       /**
+        * Returns the variable given by the name.
+        *
+        * If the variable with this name does not exist it will be created.
+        *
+        * @param id
+        *            variable name
+        * @return the variable
+        */
+       public Variable findVariable(String id) {
+               Variable rc = this.variables.getOrDefault(id, null);
+               if (rc == null) {
+                       this.variables.put(id, rc = new Variable(id));
+               }
+               return rc;
+       }
+
+       /**
+        * Sets a new text for parsing.
+        *
+        * @param text
+        *            the formula text
+        * @return the instance (for chaining)
+        */
+       public Expression reset(String text) {
+               this.scanner.reset(text);
+               return this;
+       }
+
+       /**
+        * Calculates the value of the expression.
+        *
+        * <pre>
+        * <term> ::= '(' <expr> ')' | <constant> | <variable> | <sign> <term>
+        * <sign> ::= '+' | '-'
+        * </pre>
+        *
+        * @return the value of the term
+        * @throws ParserException
+        * @throws VariantException
+        */
+       public Variant term() throws ParserException {
+               Variant rc = null;
+               Token token;
+               boolean isNegative = false;
+               do {
+                       token = this.scanner.nextNotSpaceToken();
+                       if (token.isOp(OpCode.MINUS)) {
+                               isNegative = !isNegative;
+                       }
+               } while (token.isType(TokenType.OP) && token.getOpCode().isUnary());
+
+               switch (token.getType()) {
+               case ID:
+                       rc = new Variant(findVariable(token.getString()));
+                       break;
+               case OP:
+                       switch (token.getOpCode()) {
+                       case LEFT_PARENTH:
+                               rc = expr();
+                               if (!this.scanner.getLastToken().isOp(OpCode.RIGHT_PARENT)) {
+                                       throw new ParserException(I18N.tr("missing ')'"), token, false);
+                               }
+                               break;
+                       case RIGHT_PARENT:
+                               break;
+                       default:
+                               throw new ParserException(String.format(I18N.tr("operand (e.g. constant) expected. Found operator %s"),
+                                               token.getOpCode().name()), token);
+                       }
+                       break;
+               case STRING:
+                       rc = new Variant(token.getString());
+                       break;
+               case INTEGER:
+                       rc = new Variant(token.getLongValue());
+                       break;
+               case END_OF_STRING:
+               default:
+                       break;
+               }
+               if (isNegative) {
+                       if (rc.isType(DataType.LONG)) {
+                               rc.setLongValue(-rc.getLongValue());
+                       } else if (rc.isType(DataType.DOUBLE)) {
+                               rc.setDoubleValue(-rc.getDoubleValue());
+                       }
+               }
+               return rc;
+       }
+
+}
diff --git a/src/main/java/de/republib/expr/OpCode.java b/src/main/java/de/republib/expr/OpCode.java
new file mode 100644 (file)
index 0000000..90007e1
--- /dev/null
@@ -0,0 +1,78 @@
+package de.republib.expr;
+
+public enum OpCode {
+       UNDEF(0), ASSIGN(1, false, true), //
+       PLUS(10, true), MINUS(10, true), //
+       TIMES(11), DIV(11), MOD(11), //
+       POWER(12), //
+       LEFT_PARENTH(20), RIGHT_PARENT(20), //
+       NOT(0, true);
+
+       private int priority;
+       private boolean rightAssociative;
+       private boolean unary;
+
+       /**
+        * Constructor.
+        *
+        * @param priority
+        *            priority of the operator. Higher priority binds stronger.
+        */
+       private OpCode(int priority) {
+               this.priority = priority;
+               this.rightAssociative = false;
+               this.unary = false;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param priority
+        *            priority of the operator. Higher priority binds stronger.
+        * @param isUnary
+        *            <i>true</i>: the operator is an unary operator. May be a
+        *            binary operator too
+        */
+       private OpCode(int priority, boolean isUnary) {
+               this.priority = priority;
+               this.rightAssociative = false;
+               this.unary = isUnary;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param priority
+        *            priority of the operator. Higher priority binds stronger.
+        * @param isUnary
+        *            <i>true</i>: the operator is an unary operator. May be a
+        *            binary operator too
+        * @param rightAssociative
+        *            <i>true</i>: the right term must be evaluated before the
+        *            operation can be done. Typical example is assignment '='
+        */
+       private OpCode(int priority, boolean isUnary, boolean rightAssociative) {
+               this.priority = priority;
+               this.rightAssociative = rightAssociative;
+               this.unary = isUnary;
+       }
+
+       /**
+        * @return the priority
+        */
+       public int getPriority() {
+               return this.priority;
+       }
+
+       /**
+        * @return the rightAssociative
+        */
+       public boolean isRightAssociative() {
+               return this.rightAssociative;
+       }
+
+       public boolean isUnary() {
+               // TODO Auto-generated method stub
+               return false;
+       }
+}
diff --git a/src/main/java/de/republib/expr/ParserException.java b/src/main/java/de/republib/expr/ParserException.java
new file mode 100644 (file)
index 0000000..d871be0
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+/**
+ * Handles scanner errors like "unknown token".
+ *
+ * @author hm
+ *
+ */
+public class ParserException extends Exception {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructs the message of the exception.
+        *
+        * @param message
+        *            message describing the error
+        * @param token
+        *            additional information about the context
+        * @param startPosition
+        *            <i>true</i>: the start position is the interesting point<br>
+        *            <i>false</i>: the positon behind the token is the interesting
+        *            point
+        * @return the message with position information
+        */
+       private static String buildMessage(String message, Token token, boolean startPosition) {
+               String rc;
+               if (startPosition) {
+                       rc = String.format("%d-%d ==> [%s]: %s", token.getLineNo(), token.getPosition(), token.toString(), message);
+               } else {
+                       rc = String.format("%d-%d [%s] <==: %s", token.getLineNo(), token.getPosition(), token.toString(), message);
+               }
+               return rc;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param message
+        *            message describing the error
+        * @param token
+        *            additional information about the context
+        */
+       public ParserException(String message, Token token) {
+               super(ParserException.buildMessage(message, token, true));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param message
+        *            message describing the error
+        * @param token
+        *            additional information about the context
+        * @param startPosition
+        *            <i>true</i>: the start position is the interesting point<br>
+        *            <i>false</i>: the positon behind the token is the interesting
+        *            point
+        */
+       public ParserException(String message, Token token, boolean startPosition) {
+               super(ParserException.buildMessage(message, token, startPosition));
+       }
+}
diff --git a/src/main/java/de/republib/expr/Scanner.java b/src/main/java/de/republib/expr/Scanner.java
new file mode 100644 (file)
index 0000000..9f39431
--- /dev/null
@@ -0,0 +1,254 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+import de.republib.util.I18N;
+
+/**
+ * Divides a text into tokens to parse formulas.
+ *
+ * @author hm
+ *
+ */
+public class Scanner {
+       int position = 0;
+       int lineNo = 1;
+       String text;
+       Token[] tokens = new Token[3];
+       int indexToken = 0;
+       Token lastToken = null;
+       StringBuilder string = new StringBuilder(8096);
+
+       /**
+        * Constructor.
+        *
+        * @param text
+        *            the text to parse
+        */
+       public Scanner(final String text) {
+               this.text = text;
+               for (int ix = 0; ix < this.tokens.length; ix++) {
+                       this.tokens[ix] = new Token();
+               }
+       }
+
+       /**
+        * @return the lastToken
+        */
+       public Token getLastToken() {
+               return this.lastToken;
+       }
+
+       /**
+        * Handles a escaped character of a string, e.g. '\n'.
+        *
+        * @param buffer
+        *            the character will appended to this buffer
+        */
+       private void handleEscapedChar(StringBuilder buffer) {
+               char cc;
+               int value;
+               int maxLength;
+               switch (cc = this.text.charAt(this.position++)) {
+               case 'b':
+                       buffer.append('\b');
+                       break;
+               case 'f':
+                       buffer.append('\f');
+                       break;
+               case 't':
+                       buffer.append('\t');
+                       break;
+               case 'n':
+                       buffer.append('\n');
+                       break;
+               case 'r':
+                       buffer.append('\r');
+                       break;
+               case '0': {
+                       value = 0;
+                       maxLength = 3;
+                       while (maxLength-- > 0 && this.position < this.text.length()
+                                       && (cc = this.text.charAt(this.position++)) >= '0' && cc < '9') {
+                               value = value * 8 + cc - '0';
+                       }
+                       buffer.append((char) value);
+                       break;
+               }
+               case 'X':
+               case 'x':
+                       value = 0;
+                       maxLength = 2;
+                       while (maxLength-- > 0 && this.position < this.text.length()
+                                       && ((cc = this.text.charAt(this.position)) >= '0' && cc < '9' || cc >= 'a' && cc <= 'f'
+                                                       || cc >= 'A' && cc <= 'F')) {
+                               ++this.position;
+                               final int digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10;
+                               value = value * 16 + digit;
+                       }
+                       buffer.append((char) value);
+                       break;
+               case 'U':
+               case 'u':
+                       value = 0;
+                       maxLength = 4;
+                       while (maxLength-- > 0 && this.position < this.text.length()
+                                       && ((cc = this.text.charAt(this.position++)) >= '0' && cc < '9' || cc >= 'a' && cc <= 'f'
+                                                       || cc >= 'A' && cc <= 'F')) {
+                               final int digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10;
+                               value = value * 16 + digit;
+                       }
+                       buffer.append((char) value);
+                       break;
+               case '\\':
+               default:
+                       buffer.append(cc);
+                       break;
+               }
+       }
+
+       private boolean handleOp(char cc, Token token) {
+               OpCode op = OpCode.UNDEF;
+               final int start = this.position - 1;
+               boolean rc = true;
+               switch (cc) {
+               case '+':
+                       op = OpCode.PLUS;
+                       break;
+               case '-':
+                       op = OpCode.MINUS;
+                       break;
+               case '*':
+                       if (this.position < this.text.length() && this.text.charAt(this.position) == '*') {
+                               op = OpCode.POWER;
+                               this.position++;
+                       } else {
+                               op = OpCode.TIMES;
+                       }
+                       break;
+               case '/':
+                       op = OpCode.DIV;
+                       break;
+               case '%':
+                       op = OpCode.MOD;
+                       break;
+               case '(':
+                       op = OpCode.LEFT_PARENTH;
+                       break;
+               case ')':
+                       op = OpCode.RIGHT_PARENT;
+                       break;
+               default:
+                       rc = false;
+                       break;
+               }
+               if (rc) {
+                       token.setOp(this.text.substring(start, this.position), op);
+               }
+               return rc;
+       }
+
+       /**
+        * Returns the next token which is not a SPACE token.
+        *
+        * @return the next token
+        * @throws ParserException
+        */
+       public Token nextNotSpaceToken() throws ParserException {
+               Token token;
+               while ((token = nextToken()).isType(TokenType.SPACE)) {
+                       // do nothing
+               }
+               return token;
+       }
+
+       /**
+        * Scans the next token.
+        *
+        * @return the next token
+        * @throws ParserException
+        */
+       public Token nextToken() throws ParserException {
+               final Token token = this.tokens[this.indexToken++];
+               final int tokenStart = this.position;
+               if (this.indexToken >= this.tokens.length) {
+                       this.indexToken = 0;
+               }
+               token.setPosition(this.position);
+               token.setLineNo(this.lineNo);
+               if (this.position >= this.text.length()) {
+                       token.setEndOfString();
+               } else {
+                       char cc = this.text.charAt(this.position++);
+                       if (Character.isWhitespace(cc)) {
+                               if (cc == '\n') {
+                                       ++this.lineNo;
+                               }
+                               while (this.position < this.text.length()
+                                               && Character.isWhitespace(cc = this.text.charAt(this.position))) {
+                                       if (cc == '\n') {
+                                               ++this.lineNo;
+                                       }
+                                       ++this.position;
+                               }
+                               token.setSpace(this.text.substring(tokenStart, this.position));
+                       } else if (Character.isDigit(cc)) {
+                               long value = cc - '0';
+                               long digit;
+                               if (this.position < this.text.length() && (cc = this.text.charAt(this.position)) == 'x' || cc == 'X') {
+                                       ++this.position;
+                                       while (this.position < this.text.length()
+                                                       && (Character.isDigit(cc = this.text.charAt(this.position)) || cc >= 'A' && cc <= 'F'
+                                                                       || cc >= 'a' && cc <= 'f')) {
+                                               this.position++;
+                                               digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10;
+                                               value = 16 * value + digit;
+                                       }
+                               } else {
+                                       while (this.position < this.text.length()
+                                                       && Character.isDigit(cc = this.text.charAt(this.position))) {
+                                               this.position++;
+                                               value = 10 * value + cc - '0';
+                                       }
+                               }
+                               token.setLong(value);
+                       } else if (cc == '\'' || cc == '"') {
+                               this.string.setLength(0);
+                               final char delimiter = cc;
+                               while (this.position < this.text.length() && (cc = this.text.charAt(this.position++)) != delimiter) {
+                                       if (cc != '\\') {
+                                               this.string.append(cc);
+                                       } else {
+                                               handleEscapedChar(this.string);
+                                       }
+                               }
+                               token.setString(this.string.toString(), this.text.substring(tokenStart, this.position), delimiter);
+                       } else if (Character.isJavaIdentifierStart(cc)) {
+                               while (this.position < this.text.length()
+                                               && Character.isJavaIdentifierPart(cc = this.text.charAt(this.position))) {
+                                       ++this.position;
+                               }
+                               token.setId(this.text.substring(tokenStart, this.position));
+                       } else if (!handleOp(cc, token)) {
+                               token.setUnknown("" + cc);
+                               throw new ParserException(I18N.tr("unknown token"), token);
+                       }
+               }
+               return token;
+       }
+
+       /**
+        * Resets the instance with a new text and position 0.
+        *
+        * @param text
+        *            the input text to scan
+        * @return the instance (for chaining)
+        */
+       public Scanner reset(String text) {
+               this.text = text;
+               this.position = 0;
+               this.lineNo = 1;
+               return this;
+       }
+}
diff --git a/src/main/java/de/republib/expr/Token.java b/src/main/java/de/republib/expr/Token.java
new file mode 100644 (file)
index 0000000..1aca0af
--- /dev/null
@@ -0,0 +1,269 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Describes a syntactical element of a formula.
+ *
+ * @author hm
+ *
+ */
+public class Token {
+       static Logger logger = LoggerFactory.getLogger(Token.class);
+       protected TokenType type = TokenType.UNDEF;
+       protected long longValue = 0;
+       protected String string = null;
+       /// string in printable form, with delimiters and escaped characters,
+       /// e.g."x\\n"
+       protected String rawString = null;
+       protected char delimiter = '\0';
+       protected OpCode opCode = OpCode.UNDEF;
+       protected int position;
+       protected int lineNo;
+
+       /**
+        * Constructor.
+        */
+       public Token() {
+
+       }
+
+       /**
+        * Returns a flat copy of the instance.
+        * 
+        * @return a copy of th instance
+        */
+       public Token copy() {
+               Token rc = null;
+               try {
+                       rc = (Token) clone();
+               } catch (final CloneNotSupportedException e) {
+                       Token.logger.error("Token.copy()", e);
+               }
+               return rc;
+       }
+
+       /**
+        * @return the delimiter
+        */
+       public char getDelimiter() {
+               return this.delimiter;
+       }
+
+       /**
+        * @return the lineNo
+        */
+       public int getLineNo() {
+               return this.lineNo;
+       }
+
+       /**
+        * @return the longValue
+        */
+       public long getLongValue() {
+               return this.longValue;
+       }
+
+       /**
+        * @return the opCode
+        */
+       public OpCode getOpCode() {
+               return this.opCode;
+       }
+
+       /**
+        * @return the position
+        */
+       public int getPosition() {
+               return this.position;
+       }
+
+       /**
+        * @return the rawString
+        */
+       public String getRawString() {
+               return this.rawString;
+       }
+
+       /**
+        * @return the string
+        */
+       public String getString() {
+               return this.string;
+       }
+
+       /**
+        * @return the type
+        */
+       public TokenType getType() {
+               return this.type;
+       }
+
+       /**
+        * Tests whether the token has a given type.
+        *
+        * @param type
+        *            token type to compare
+        * @return <i>true</i>the type matches
+        */
+       public boolean isType(TokenType type) {
+               return this.type == type;
+       }
+
+       /**
+        * Tests whether the token is a given operator.
+        *
+        * @param opCode
+        *            operator to compare
+        * @return <i>true</i>: the token is an operator and has the given opcode
+        */
+       public boolean isOp(OpCode opCode) {
+               final boolean rc = this.type == TokenType.OP && this.opCode == opCode;
+               return rc;
+       }
+
+       /**
+        * Sets the token as identifier.
+        *
+        * @param id
+        *            name of the identifier
+        * @param delimiter
+        * @return the instance (for chaining)
+        */
+       public Token setEndOfString() {
+               this.type = TokenType.END_OF_STRING;
+               return this;
+       }
+
+       /**
+        * Sets the token as identifier.
+        *
+        * @param id
+        *            name of the identifier
+        * @param delimiter
+        * @return the instance (for chaining)
+        */
+       public Token setId(String id) {
+               this.type = TokenType.ID;
+               this.string = id;
+               return this;
+       }
+
+       /**
+        * @param lineNo
+        *            the lineNo to set
+        */
+       public void setLineNo(int lineNo) {
+               this.lineNo = lineNo;
+       }
+
+       /**
+        * Sets the token as long constant.
+        *
+        * @param value
+        *            the long value
+        * @return the instance (for chaining)
+        */
+       public Token setLong(long value) {
+               this.type = TokenType.INTEGER;
+               this.longValue = value;
+               return this;
+       }
+
+       /**
+        * Sets the token as operator.
+        *
+        * @param string
+        *            the string of the operator, e.g. "+"
+        * @param op
+        *            the operator like "+"
+        * @return the instance (for chaining)
+        */
+       public Token setOp(String string, OpCode op) {
+               this.type = TokenType.OP;
+               this.string = string;
+               this.opCode = op;
+               return this;
+       }
+
+       /**
+        * @param position
+        *            the position to set
+        */
+       public void setPosition(int position) {
+               this.position = position;
+       }
+
+       /**
+        * Sets the token as space sequence.
+        *
+        * @param spaces
+        *            the space sequence
+        * @return the instance (for chaining)
+        */
+       public Token setSpace(String spaces) {
+               this.type = TokenType.SPACE;
+               this.string = spaces;
+               return this;
+       }
+
+       /**
+        * Sets the token as string.
+        *
+        * @param spaces
+        *            the space sequence
+        * @return the instance (for chaining)
+        */
+       public Token setString(String string, String rawString, char delimiter) {
+               this.type = TokenType.STRING;
+               this.string = string;
+               this.rawString = rawString;
+               this.delimiter = delimiter;
+               return this;
+       }
+
+       /**
+        * Sets the token as space sequence.
+        *
+        * @param spaces
+        *            the space sequence
+        * @return the instance (for chaining)
+        */
+       public Token setUnknown(String info) {
+               this.type = TokenType.UNKNOWN;
+               this.string = info;
+               return this;
+       }
+
+       @Override
+       public String toString() {
+               String rc = null;
+               switch (this.type) {
+               case END_OF_STRING:
+                       rc = "EOS";
+                       break;
+               case INTEGER:
+                       rc = String.valueOf(this.getLongValue());
+                       break;
+               case STRING:
+                       rc = this.getRawString();
+                       break;
+               case UNKNOWN:
+               case ID:
+               case OP:
+                       rc = this.getString();
+                       break;
+               case SPACE:
+                       rc = "<spaces>";
+                       break;
+               default:
+                       rc = "?";
+                       break;
+               }
+               return rc;
+       }
+}
diff --git a/src/main/java/de/republib/expr/TokenType.java b/src/main/java/de/republib/expr/TokenType.java
new file mode 100644 (file)
index 0000000..8a87ce6
--- /dev/null
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+/**
+ * @author hm
+ *
+ */
+public enum TokenType {
+       UNDEF, INTEGER, OP, ID, STRING, SPACE, END_OF_STRING, UNKNOWN
+}
diff --git a/src/main/java/de/republib/expr/Variable.java b/src/main/java/de/republib/expr/Variable.java
new file mode 100644 (file)
index 0000000..ccd6fe1
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+/**
+ * Manages a variable.
+ *
+ * A variable is a name and a value.
+ *
+ * @author hm
+ *
+ */
+public class Variable {
+       protected String id;
+       protected Variant value;
+
+       /**
+        * Constructor.
+        *
+        * @param id
+        *            name of the variable
+        */
+       public Variable(String id) {
+               this.id = id;
+               this.value = new Variant("");
+       }
+
+       /**
+        * Assigns a new value to the instance.
+        *
+        * @param value
+        */
+       public void assign(Variant value) {
+               this.value.redefine(value);
+       }
+
+       /**
+        * @return the id
+        */
+       public String getId() {
+               return this.id;
+       }
+
+       /**
+        * @return the value
+        */
+       public Variant getValue() {
+               return this.value;
+       }
+}
diff --git a/src/main/java/de/republib/expr/Variant.java b/src/main/java/de/republib/expr/Variant.java
new file mode 100644 (file)
index 0000000..02102ac
--- /dev/null
@@ -0,0 +1,541 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+import de.republib.util.I18N;
+
+/**
+ * Implements a container for a value of different data types.
+ *
+ * @author hm
+ *
+ */
+public class Variant {
+       protected DataType type;
+       protected long longValue = Long.MIN_VALUE;
+       protected double doubleValue = Double.NaN;
+       protected Boolean boolValue = null;
+       protected String stringValue;
+       protected Object objectValue;
+       protected Variable variableValue;
+
+       /**
+        * Constructor.
+        *
+        * @param value
+        *            the instance becomes the type BOOLEAN with this value
+        */
+       public Variant(boolean value) {
+               this.boolValue = value;
+               this.type = DataType.BOOLEAN;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param longValue
+        *            the instance becomes the type INTEGER with this value
+        */
+       public Variant(double value) {
+               this.doubleValue = value;
+               this.type = DataType.DOUBLE;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param value
+        *            the instance becomes the type INTEGER with this value
+        */
+       public Variant(long value) {
+               this.longValue = value;
+               this.type = DataType.LONG;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param value
+        *            the instance becomes the type OBJECT with this value
+        */
+       public Variant(Object value) {
+               this.objectValue = value;
+               this.type = DataType.STRING;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param string
+        *            the instance becomes the type STRING with this value
+        */
+       public Variant(String string) {
+               this.stringValue = string;
+               this.type = DataType.STRING;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param value
+        *            the instance becomes the type VARIABLE with this value
+        */
+       public Variant(Variable value) {
+               this.variableValue = value;
+               this.type = DataType.VARIABLE;
+       }
+
+       /**
+        * Returns the value as long integer.
+        *
+        * @param defaultValue
+        *            return code for unexpected states
+        * @return a long representation of the value or the default value
+        */
+       public long asLong(long defaultValue) {
+               long rc = defaultValue;
+               if (this.longValue != Long.MIN_VALUE) {
+                       rc = this.longValue;
+               } else {
+                       switch (this.type) {
+                       case BOOLEAN:
+                               rc = this.longValue = this.boolValue ? 1L : 0L;
+                               break;
+                       case DOUBLE:
+                               rc = this.longValue = Math.round(this.doubleValue);
+                               break;
+                       case LONG:
+                               rc = this.longValue;
+                               break;
+                       case STRING: {
+                               try {
+                                       final Token token = new Scanner(this.stringValue).nextNotSpaceToken();
+                                       if (token.isType(TokenType.INTEGER)) {
+                                               rc = this.longValue = token.getLongValue();
+                                       }
+                               } catch (final ParserException e) {
+                                       // do nothing
+                               }
+                               break;
+                       }
+                       case VARIABLE:
+                               rc = this.longValue = this.variableValue.getValue().asLong(defaultValue);
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               return rc;
+       }
+
+       /**
+        * Returns the value as string.
+        *
+        * @return a string representation of the value
+        */
+       public String asString() {
+               if (this.stringValue == null) {
+                       switch (this.type) {
+                       case BOOLEAN:
+                               this.stringValue = this.boolValue ? "true" : "false";
+                               break;
+                       case DOUBLE:
+                               this.stringValue = String.valueOf(this.doubleValue);
+                               break;
+                       case LONG:
+                               this.stringValue = String.valueOf(this.longValue);
+                               break;
+                       case OBJECT:
+                               this.stringValue = this.objectValue == null ? "" : this.objectValue.toString();
+                               break;
+                       case VARIABLE:
+                               this.stringValue = this.variableValue.getValue().asString();
+                               break;
+                       case STRING:
+                       default:
+                               this.stringValue = "";
+                               break;
+                       }
+               }
+               return this.stringValue;
+       }
+
+       /**
+        * @return the boolValue
+        */
+       public Boolean getBoolValue() {
+               return this.boolValue;
+       }
+
+       /**
+        * @return the doubleValue
+        */
+       public double getDoubleValue() {
+               return this.doubleValue;
+       }
+
+       /**
+        * @return the longValue
+        */
+       public long getLongValue() {
+               return this.longValue;
+       }
+
+       /**
+        * @return the objectValue
+        */
+       public Object getObjectValue() {
+               return this.objectValue;
+       }
+
+       /**
+        * @return the string
+        */
+       public String getStringValue() {
+               return this.stringValue;
+       }
+
+       /**
+        * @return the type
+        */
+       public DataType getType() {
+               return this.type;
+       }
+
+       /**
+        * @return the variableValue
+        */
+       public Variable getVariableValue() {
+               return this.variableValue;
+       }
+
+       /**
+        * Tests whether the data type is equal to a given type.
+        *
+        * @param expected
+        *            the type to compare
+        * @return <i>true</i>: the type is the expected
+        */
+       public boolean isType(DataType expected) {
+               return this.type == expected;
+       }
+
+       /**
+        * Does an operation with another value.
+        *
+        * @param op
+        *            the operation to do
+        * @param operand
+        *            the other value
+        * @return the instance (for chaining)
+        * @throws VariantException
+        */
+       Variant binaryOperation(OpCode op, Variant operand) throws VariantException {
+               switch (op) {
+               case ASSIGN:
+                       if (this.type != DataType.VARIABLE) {
+                               throw new VariantException(I18N.tr("Assignment misses a rvalue (e.g. a variable) on the left side"));
+                       }
+                       this.variableValue.assign(operand);
+                       break;
+               case DIV:
+               case MOD:
+                       if (this.type == DataType.LONG) {
+                               final long op2 = operand.asLong(0);
+                               if (op2 == 0L) {
+                                       throw new VariantException(I18N.tr("division by 0"));
+                               } else {
+                                       if (op == OpCode.MOD) {
+                                               this.longValue %= op2;
+                                       } else {
+                                               this.longValue /= op2;
+                                       }
+                               }
+                       } else {
+                               wrongDataType(this, op, operand);
+                       }
+                       break;
+               case MINUS:
+                       if (this.type == DataType.LONG) {
+                               final long op2 = operand.asLong(0);
+                               this.longValue -= op2;
+                       } else {
+                               wrongDataType(this, op, operand);
+                       }
+                       break;
+               case PLUS:
+                       if (this.type == DataType.LONG) {
+                               final long op2 = operand.asLong(0);
+                               this.longValue += op2;
+                       } else if (this.type == DataType.STRING) {
+                               this.stringValue += operand.asString();
+                       } else {
+                               wrongDataType(this, op, operand);
+                       }
+                       break;
+               case POWER:
+                       if (this.type == DataType.LONG) {
+                               long op2 = operand.asLong(0);
+                               if (op2 < 0) {
+                                       throw new VariantException(I18N.tr("exponent < 0: ") + operand.asString());
+                               } else if (op2 == 0) {
+                                       this.longValue = 1;
+                               } else if (op2 > 100) {
+                                       throw new VariantException(I18N.tr("exponent too large: ") + operand.asString());
+                               } else {
+                                       long value = this.longValue;
+                                       while (op2-- > 1) {
+                                               value *= this.longValue;
+                                       }
+                                       this.longValue = value;
+                               }
+                       } else {
+                               wrongDataType(this, op, operand);
+                       }
+                       break;
+               case TIMES:
+                       if (this.type == DataType.LONG) {
+                               final long op2 = operand.asLong(0);
+                               this.longValue *= op2;
+                       } else if (this.type == DataType.STRING) {
+                               long factor = operand.asLong(0);
+                               if (factor > 100000) {
+                                       throw new VariantException(I18N.tr("string multiplier too large: ") + factor);
+                               } else {
+                                       final StringBuilder buffer = new StringBuilder((int) factor * this.stringValue.length());
+                                       while (factor-- > 0) {
+                                               buffer.append(this.stringValue);
+                                       }
+                                       this.stringValue = buffer.toString();
+                               }
+                       } else {
+                               wrongDataType(this, op, operand);
+                       }
+                       break;
+               default:
+                       throw new VariantException(String.format(I18N.tr("unexpected operation %s"), op.name()));
+               }
+               return this;
+       }
+
+       /**
+        * Takes the datat ype from another instance.
+        *
+        * @param source
+        * @return the instance (for chaining)
+        */
+       public Variant redefine(Variant source) {
+               switch (source.getType()) {
+               case BOOLEAN:
+                       redefineBool(source.getBoolValue());
+                       break;
+               case DOUBLE:
+                       redefineDouble(source.getDoubleValue());
+                       break;
+               case LONG:
+                       redefineLong(source.getLongValue());
+                       break;
+               case OBJECT:
+                       redefineObject(source.getObjectValue());
+                       break;
+               case STRING:
+                       redefineString(source.getStringValue());
+                       break;
+               case VARIABLE:
+                       redefineVariable(source.getVariableValue());
+                       break;
+               default:
+                       break;
+               }
+               return this;
+       }
+
+       /**
+        * Change the data type to boolean.
+        *
+        * @param value
+        *            the boolean value
+        * @return the instance (for chaining)
+        */
+       public Variant redefineBool(Boolean value) {
+               this.type = DataType.BOOLEAN;
+               this.stringValue = null;
+               this.longValue = Long.MIN_VALUE;
+               this.boolValue = value;
+               this.doubleValue = Double.NaN;
+               this.objectValue = null;
+               this.variableValue = null;
+               return this;
+       }
+
+       /**
+        * Change the data type to double.
+        *
+        * @param value
+        *            the double value
+        * @return the instance (for chaining)
+        */
+       public Variant redefineDouble(double value) {
+               this.type = DataType.DOUBLE;
+               this.stringValue = null;
+               this.longValue = Long.MIN_VALUE;
+               this.boolValue = null;
+               this.doubleValue = value;
+               this.objectValue = null;
+               this.variableValue = null;
+               return this;
+       }
+
+       /**
+        * Change the data type to integer.
+        *
+        * @param value
+        *            the value value
+        * @return the instance (for chaining)
+        */
+       public Variant redefineLong(long value) {
+               this.type = DataType.LONG;
+               this.stringValue = null;
+               this.longValue = value;
+               this.boolValue = null;
+               this.doubleValue = Double.NaN;
+               this.objectValue = null;
+               this.variableValue = null;
+               return this;
+       }
+
+       /**
+        * Change the data type to string.
+        *
+        * @param value
+        *            the string value
+        * @return the instance (for chaining)
+        */
+       public Variant redefineObject(Object value) {
+               this.type = DataType.OBJECT;
+               this.stringValue = null;
+               this.longValue = Long.MIN_VALUE;
+               this.boolValue = null;
+               this.doubleValue = Double.NaN;
+               this.objectValue = value;
+               this.variableValue = null;
+               return this;
+       }
+
+       /**
+        * Change the data type to string.
+        *
+        * @param value
+        *            the string value
+        * @return the instance (for chaining)
+        */
+       public Variant redefineString(String value) {
+               this.type = DataType.STRING;
+               this.stringValue = value;
+               this.longValue = Long.MIN_VALUE;
+               this.boolValue = null;
+               this.doubleValue = Double.NaN;
+               this.objectValue = null;
+               this.variableValue = null;
+               return this;
+       }
+
+       /**
+        * Change the data type to VARIABLE.
+        *
+        * @param value
+        *            the variable
+        * @return the instance (for chaining)
+        */
+       public Variant redefineVariable(Variable value) {
+               this.type = DataType.VARIABLE;
+               this.stringValue = null;
+               this.longValue = Long.MIN_VALUE;
+               this.boolValue = null;
+               this.doubleValue = Double.NaN;
+               this.objectValue = null;
+               this.variableValue = value;
+               return this;
+       }
+
+       /**
+        * @param boolValue
+        *            the boolValue to set
+        */
+       public void setBoolValue(boolean boolValue) {
+               this.boolValue = boolValue;
+       }
+
+       /**
+        * @param boolValue
+        *            the boolValue to set
+        */
+       public void setBoolValue(Boolean boolValue) {
+               this.boolValue = boolValue;
+       }
+
+       /**
+        * @param doubleValue
+        *            the doubleValue to set
+        */
+       public void setDoubleValue(double doubleValue) {
+               this.doubleValue = doubleValue;
+       }
+
+       /**
+        * @param longValue
+        *            the longValue to set
+        */
+       public void setLongValue(long longValue) {
+               this.longValue = longValue;
+       }
+
+       /**
+        * @param objectValue
+        *            the objectValue to set
+        */
+       public void setObjectValue(Object objectValue) {
+               this.objectValue = objectValue;
+       }
+
+       /**
+        * @param string
+        *            the string to set
+        */
+       public void setStringValue(String string) {
+               this.stringValue = string;
+       }
+
+       /**
+        * @param type
+        *            the type to set
+        */
+       public void setType(DataType type) {
+               this.type = type;
+       }
+
+       /**
+        * @param variableValue
+        *            the variableValue to set
+        */
+       public void setVariableValue(Variable variableValue) {
+               this.variableValue = variableValue;
+       }
+
+       /**
+        * Informs about a wrong combination of data types and/or operator.
+        *
+        * @param op1
+        *            first operand
+        * @param op
+        *            operator
+        * @param op2
+        *            second operand
+        * @throws VariantException
+        */
+       protected void wrongDataType(Variant op1, OpCode op, Variant op2) throws VariantException {
+               throw new VariantException(String.format(I18N.tr("wrong operators (%s - %s) for operator %s"),
+                               op1.getType().name(), op2.getType().name(), op.name()));
+       }
+}
diff --git a/src/main/java/de/republib/expr/VariantException.java b/src/main/java/de/republib/expr/VariantException.java
new file mode 100644 (file)
index 0000000..bf37af5
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ *
+ */
+package de.republib.expr;
+
+/**
+ * Handles errors in <i>Variant</i>
+ *
+ * @author hm
+ *
+ */
+public class VariantException extends Exception {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructor.
+        *
+        * @param message
+        *            describes the reason of the error
+        */
+       VariantException(String message) {
+               super(message);
+       }
+}
index 0e1f9c8d86d40e36f14af091cd4ec54c4dbe8e93..1b13b161cd2a6b72def1d26430dd69e216906372 100644 (file)
@@ -170,14 +170,16 @@ public class Channel {
                graphics.setColor(Channel.colorLegend);
                final int x = this.x0 + this.width / 2;
                final int y = this.y0 + this.parent.getFontSizeLegend() + 5;
-               final IPairData data = this.curves.get(0);
-               graphics.drawString(data.getName(), x, y);
-               String text = String.format("(%.1g, %.1g)", data.getX(0), data.getY(0));
-               graphics.drawString(text, this.x0 + 5, this.y0 + this.getHeight() - this.parent.getFontSizeLegend());
-               final int lastIx = data.getSteps() - 1;
-               text = String.format("(%.1g, %.1g)", data.getX(lastIx), data.getY(lastIx));
-               final int width = graphics.getFontMetrics().stringWidth(text);
-               graphics.drawString(text, this.x0 + this.width - width - 5, y);
+               if (this.curves.size() > 0) {
+                       final IPairData data = this.curves.get(0);
+                       graphics.drawString(data.getName(), x, y);
+                       String text = String.format("(%.1g, %.1g)", data.getX(0), data.getY(0));
+                       graphics.drawString(text, this.x0 + 5, this.y0 + this.getHeight() - this.parent.getFontSizeLegend());
+                       final int lastIx = data.getSteps() - 1;
+                       text = String.format("(%.1g, %.1g)", data.getX(lastIx), data.getY(lastIx));
+                       final int width = graphics.getFontMetrics().stringWidth(text);
+                       graphics.drawString(text, this.x0 + this.width - width - 5, y);
+               }
        }
 
        /**
diff --git a/src/main/java/de/republib/gui/GuiUtils.java b/src/main/java/de/republib/gui/GuiUtils.java
new file mode 100644 (file)
index 0000000..afed63b
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ *
+ */
+package de.republib.gui;
+
+import javax.swing.JTextField;
+
+/**
+ * Helper functions for Graphical User Interface elements.
+ *
+ * Only static methods.
+ *
+ * @author hm
+ *
+ */
+public class GuiUtils {
+       /**
+        * Gets an integer from a text field.
+        *
+        * @param field
+        *            the field holding the integer
+        * @param defaultValue
+        *            return value if the field does not contain an integer
+        * @param repair
+        *            <i>true</i>If the field value is not an integer the default
+        *            value is set as field value
+        * @return <i>defaultValue</i> the field value has not a correct integer<br>
+        *         the value of the field as integer
+        */
+       public static int numberOf(JTextField field, int defaultValue, boolean repair) {
+               int rc = defaultValue;
+               try {
+                       rc = Integer.parseInt(field.getText());
+               } catch (final NumberFormatException e) {
+                       if (repair) {
+                               field.setText(String.valueOf(defaultValue));
+                       }
+               }
+               return rc;
+       }
+}
index 1619e4fef4c07060d25c9a4f48e84e94b8e0da8f..aabd91c139f780bbabcc830769c932398fea9e0e 100644 (file)
@@ -43,8 +43,8 @@ public class MultiChannelSheet extends JPanel {
         */
        private static final long serialVersionUID = 1L;
 
-       private final int columnCount = 2;
-       private final int rowCount = 4;
+       private int columnCount = 2;
+       private int rowCount = 4;
        private final ArrayList<Channel> channels = new ArrayList<>();
        private final int widthGridLine = 1;
        private final Color colorGridLine = Color.BLACK;
@@ -187,21 +187,28 @@ public class MultiChannelSheet extends JPanel {
        }
 
        /**
-        * @param channelCount
-        *            the channelCount to set
+        * @param rows
+        *            count of rows
+        * @param columns
+        *            count of columns
         */
-       public void setChannelCount(int channelCount) {
+       public void setChannelCount(int rows, int columns) {
                boolean changed = false;
-               while (this.channels.size() > channelCount) {
-                       this.channels.remove(this.channels.size() - 1);
-                       changed = true;
-               }
-               while (this.channels.size() < channelCount) {
-                       this.channels.add(new Channel(this.channels.size(), this));
-                       changed = true;
-               }
-               if (changed) {
-                       resizeChannels();
+               if (rows > 0 && columns > 0) {
+                       final int channelCount = rows * columns;
+                       this.rowCount = rows;
+                       this.columnCount = columns;
+                       while (this.channels.size() > channelCount) {
+                               this.channels.remove(this.channels.size() - 1);
+                               changed = true;
+                       }
+                       while (this.channels.size() < channelCount) {
+                               this.channels.add(new Channel(this.channels.size(), this));
+                               changed = true;
+                       }
+                       if (changed) {
+                               resizeChannels();
+                       }
                }
        }
 
index 2e072ce3ec2f784b19b655a838f97947a7bc904e..2a8aea629e9256a2d5faaca4c51482d8f3303067 100644 (file)
@@ -76,7 +76,9 @@ public class TcpClient {
                        buffer.setLength(0 + 0);
                        length = this.inStream.read(buffer.getBuffer(), 0, buffer.getCapacity());
                        buffer.setLength(length);
-                       if (this.logger.isDebugEnabled()) {
+                       if (buffer.startsWith("ERR")) {
+                               this.logger.error("received: " + buffer.toUtf8());
+                       } else if (this.logger.isDebugEnabled()) {
                                this.logger.debug(String.format("received: %s[%d]", buffer.toUtf8(4), length));
                        }
                } catch (final IOException e) {
diff --git a/src/main/java/de/republib/pinet/Function.java b/src/main/java/de/republib/pinet/Function.java
new file mode 100644 (file)
index 0000000..5498528
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ *
+ */
+package de.republib.pinet;
+
+/**
+ * @author hm
+ *
+ */
+public enum Function {
+       CONSTANT(0, 'c'), SIN(1, 's'), FALLING_SLOPE(3, 'f'), RISING_SLOPE(2, 'r'), DUAL_SLOPE(4, 'd');
+
+       /**
+        * Search a function by its id.
+        *
+        * @param id
+        *            id to search
+        * @return null: not found<br>
+        *         otherwise: the function with the id
+        */
+       public static Function findById(int id) {
+               Function rc = null;
+               for (final Function func : Function.values()) {
+                       if (func.getId() == id) {
+                               rc = func;
+                       }
+               }
+               return rc;
+       }
+
+       private int id;
+
+       private byte code;
+
+       /**
+        * Constructor.
+        *
+        * @param id
+        *            a current number
+        * @param code
+        *            the code used in the TCP requests
+        */
+       private Function(int id, char code) {
+               this.id = id;
+               this.code = (byte) code;
+       }
+
+       /**
+        * @return the code
+        */
+       public byte getCode() {
+               return this.code;
+       }
+
+       /**
+        * @return the id
+        */
+       public int getId() {
+               return this.id;
+       }
+}
index 75831763f77ef0f05f8c87466250a49f0715295b..43ef611d0ab03fc91b31ab2a53e3784e063cf325 100644 (file)
@@ -31,7 +31,7 @@ public class GpioClient extends TcpClient {
        }
 
        /**
-        * Toggles an output pin between high and low.
+        * Lets the server toggle an output pin between high and low.
         *
         * @param pin
         *            pin for output
@@ -57,4 +57,57 @@ public class GpioClient extends TcpClient {
                rc = sendAndReceive(this.buffer);
                return rc;
        }
+
+       /*
+        */
+       /**
+        * Lets the server put Pulse Code Modulation signals to a given pin.
+        *
+        * <pre>
+        * Syntax: OPWM[pin][clock][steps][startvalue][function][functionSteps]<br>
+        * [pin]: 1 byte. Numbering like <i>PinNumber</i>.<br>
+        * [clock]: 4 byte little endian. duration of a time slice in micro seconds.<br>
+        * [steps]: 2 byte little endian, number of time slices for one period.
+        * [startvalue]: 2 byte little endian, first value when starting the sequence.
+        * [function]: 's': sin, 'r': rising slope, 'f': falling slope, 'd': dual slope
+        * [functionSteps]: 2 byte little endian, number of PWM periods defining one function period
+        * [count]: 4 byte little endian.number of function periods
+        * </pre>
+        *
+        * @param pin
+        *            pin for output
+        * @param clock
+        *            minimal time slice of the pwm period in microseconds
+        * @param steps
+        *            number of time slices
+        * @param startValue
+        *            value of the first PWM period (in steps)
+        * @param function
+        *            function controlling the pwm values
+        * @param functionSteps
+        *            number of steps
+        * @param count
+        *            number of function periods
+        * @return the answer from the server
+        */
+       public DynBytes pwmOutput(PinNumber pin, int period, double startValue, Function function, int functionSteps,
+                       int count) {
+               DynBytes rc = null;
+               if (this.logger.isDebugEnabled()) {
+                       this.logger
+                                       .debug(String.format("output pwm: pin: %d period: %d start: %f function: %s f.steps: %d count: %d",
+                                                       pin.getNumber(), period, startValue, function.name(), functionSteps, count));
+               }
+               this.buffer.setLength(0).append("OPWM");
+               this.buffer.append((byte) pin.getNumber());
+               this.buffer.appendLittleEndian(period, 4);
+               final int value = Math.max(0, Math.min(10000, (int) Math.round(startValue * 1E4)));
+               this.buffer.appendLittleEndian(value, 2);
+               this.buffer.append(function.getCode());
+               this.buffer.appendLittleEndian(functionSteps, 2);
+               this.buffer.appendLittleEndian(count, 4);
+               rc = sendAndReceive(this.buffer);
+               return rc;
+       }
+
 }
index df3949ea28f42760bfc2bc04b24c5d30c8419d8d..9bfa0909edb030ff7d6898c00358289649cb84ab 100644 (file)
@@ -4,13 +4,20 @@ import java.awt.BorderLayout;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.GridLayout;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.LinkedList;
+import java.util.List;
 
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
 import javax.swing.JTextField;
 
+import de.republib.gui.GuiUtils;
 import de.republib.gui.MultiChannelSheet;
 import de.republib.pinet.GpioClient;
 import de.republib.util.Announcer;
@@ -21,19 +28,22 @@ import de.republib.util.MathFunction;
 /**
  * Created by hm on 03.07.16.
  */
-public class ControlCenter implements Announcer {
+public class ControlCenter implements Announcer, FocusListener {
        private GpioClient client = null;
        private JFrame frame;
-
+       private final List<JComboBox<String>> channelComboBoxes = new LinkedList<>();
        private JPanel panelCenter;
        private GPIOSettings panelGPIOSettings;
        private JPanel panelLog;
        private JLabel labelStatusLine;
-       private MultiChannelSheet panelData;
+       private MultiChannelSheet panelMultiChannels;
        private JPanel panelHead;
        private JTextField textFieldServer;
        private JTextField textFieldPort;
+       private JTextField textFieldRows;
+       private JTextField textFieldColumns;
        private JTabbedPane tabbedPane;
+       private int channelCount = 8;
 
        /**
         * Constructor.
@@ -42,6 +52,56 @@ public class ControlCenter implements Announcer {
 
        }
 
+       /**
+        * Adds a combobox with channels in the list to the internal list.
+        *
+        * @param comboBox
+        *            combobox to add
+        */
+       public void addChannelComboBox(JComboBox<String> comboBox) {
+               this.channelComboBoxes.add(comboBox);
+               fillChannelCombo(comboBox, this.channelCount);
+       }
+
+       /**
+        * Builds the list of a given channel combo box.
+        *
+        * @param combobox
+        *            combo box to modify
+        * @param count
+        *            number of channels
+        */
+       public void fillChannelCombo(JComboBox<String> combobox, int count) {
+               final String[] names = new String[count];
+               for (int ix = 0; ix < count; ix++) {
+                       names[ix] = String.valueOf(ix);
+               }
+               combobox.setModel(new DefaultComboBoxModel<>(names));
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
+        */
+       @Override
+       public void focusGained(FocusEvent e) {
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
+        */
+       @Override
+       public void focusLost(FocusEvent e) {
+               if (e.getSource() == this.textFieldRows || e.getSource() == this.textFieldColumns) {
+                       setChannelCount(GuiUtils.numberOf(this.textFieldRows, 4, true),
+                                       GuiUtils.numberOf(this.textFieldColumns, 2, true));
+               }
+
+       }
+
        /**
         * @return the client
         */
@@ -56,6 +116,14 @@ public class ControlCenter implements Announcer {
                return this.client;
        }
 
+       /**
+        * Populates the main panel with widgets.
+        *
+        * @param host
+        *            the host shown in the text field
+        * @param port
+        *            the port shown in the text field
+        */
        public void populate(String host, int port) {
                this.frame = new JFrame(I18N.tr("Pinet Control Center"));
                this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -71,11 +139,17 @@ public class ControlCenter implements Announcer {
                this.panelHead.add(this.textFieldServer = new JTextField(host));
                this.panelHead.add(new JLabel(I18N.tr("Port:")));
                this.panelHead.add(this.textFieldPort = new JTextField(String.valueOf(port)));
+               this.panelHead.add(new JLabel(I18N.tr("Rows:")));
+               this.panelHead.add(this.textFieldRows = new JTextField(String.valueOf(4)));
+               this.textFieldRows.addFocusListener(this);
+               this.panelHead.add(new JLabel(I18N.tr("Column:")));
+               this.panelHead.add(this.textFieldColumns = new JTextField(String.valueOf(2)));
+               this.textFieldColumns.addFocusListener(this);
 
                this.panelCenter.add(this.tabbedPane = new JTabbedPane());
                this.tabbedPane.addTab(I18N.tr("GPIO settings"), this.panelGPIOSettings = new GPIOSettings(this));
                this.panelGPIOSettings.populate();
-               this.tabbedPane.addTab(I18N.tr("Diagram"), this.panelData = new MultiChannelSheet());
+               this.tabbedPane.addTab(I18N.tr("Diagram"), this.panelMultiChannels = new MultiChannelSheet());
                populateCurves();
                this.tabbedPane.addTab(I18N.tr("Log"), this.panelLog = new JPanel());
        }
@@ -84,23 +158,23 @@ public class ControlCenter implements Announcer {
                FunctionPairData function = new FunctionPairData(MathFunction.X, 0.0, 10.0, 790);
                function.setFactor(9.0);
                function.setOffset(1.0);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.SIN, 0.0, 10.0, 790);
                function.setFactor(4.0);
                function.setOffset(5.0);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.COS, 0.0, 10.0, 790);
                function.setFactor(4.0);
                function.setOffset(5.0);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.LOG, 0.01, 10.0, 790);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.EXP, -5.0, 5.0, 790);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.X2, -1, 2, 790);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
                function = new FunctionPairData(MathFunction.TAN, 0.0, 1.0, 790);
-               this.panelData.addChannel(function);
+               this.panelMultiChannels.addChannel(function);
 
        }
 
@@ -133,4 +207,21 @@ public class ControlCenter implements Announcer {
                        this.labelStatusLine.setText(prefix + message);
                }
        }
+
+       /**
+        * Sets a new channel count.
+        *
+        * @param rows
+        *            number of rows of the multi channel sheet
+        * @param columns
+        *            number of columns of the multi channel sheet
+        */
+       public void setChannelCount(int rows, int columns) {
+               this.channelCount = rows * columns;
+               for (final JComboBox<String> combo : this.channelComboBoxes) {
+                       fillChannelCombo(combo, this.channelCount);
+               }
+               this.panelMultiChannels.setChannelCount(rows, columns);
+               this.panelMultiChannels.repaint();
+       }
 }
index 66fc6335fb44fc222a029c287777a71b864238b0..ef2ca04ba599ff89c5b942dc2b7a7751e5571d5a 100644 (file)
@@ -6,9 +6,16 @@ package de.republib.pinet.gui;
 import java.awt.Component;
 import java.awt.FlowLayout;
 import java.awt.GridLayout;
+import java.awt.Image;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.io.IOException;
+import java.net.URL;
 
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
@@ -18,11 +25,14 @@ import javax.swing.JTextField;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.republib.gui.GuiUtils;
+import de.republib.pinet.Function;
 import de.republib.pinet.GpioClient;
 import de.republib.pinet.PinNumber;
 import de.republib.util.Announcer;
 import de.republib.util.DynBytes;
 import de.republib.util.I18N;
+import net.miginfocom.swing.MigLayout;
 
 /**
  * Manages the register page "GPIO Settings".
@@ -38,8 +48,10 @@ public class GPIOSettings extends JPanel {
        // private final Logger logger =
        // LoggerFactory.getLogger(GPIOSettings.class);
        private GPIOTable gpioTable;
-       private OutputData panelOutputData;
-       private InputData panelInputData;
+       private OutputBlinkPanel panelOutputBlinkData;
+       private OutputPwmPanel panelOutputPwmData;
+       private InputPanel panelInputData;
+       private InputPwmPanel panelInputPwmData;
        private final ControlCenter center;
 
        /**
@@ -55,28 +67,49 @@ public class GPIOSettings extends JPanel {
        /**
         * @return the panelInputData
         */
-       public InputData getPanelInputData() {
+       public InputPanel getPanelInputData() {
                return this.panelInputData;
        }
 
+       /**
+        * @return the panelInputPwmData
+        */
+       public InputPwmPanel getPanelInputPwmData() {
+               return this.panelInputPwmData;
+       }
+
+       /**
+        * @return the panelOutputData
+        */
+       public OutputBlinkPanel getPanelOutputBlinkData() {
+               return this.panelOutputBlinkData;
+       }
+
        /**
         * @return the panelOutputData
         */
-       public OutputData getPanelOutputData() {
-               return this.panelOutputData;
+       public OutputPwmPanel getPanelOutputPwmData() {
+               return this.panelOutputPwmData;
        }
 
+       /**
+        * Populates the panel with the widgets.
+        */
        public void populate() {
 
                this.gpioTable = new GPIOTable(this);
                add(this.gpioTable);
-               final PinData panelPinData = new PinData(this.gpioTable);
+               final PinPanel panelPinData = new PinPanel(this.gpioTable);
                this.gpioTable.setPanelPinData(panelPinData);
                add(panelPinData);
-               add(this.panelOutputData = new OutputData(this.center, panelPinData));
-               this.panelOutputData.setVisible(false);
-               add(this.panelInputData = new InputData(this.center, panelPinData));
-               this.panelOutputData.setVisible(false);
+               add(this.panelOutputBlinkData = new OutputBlinkPanel(this.center, panelPinData));
+               this.panelOutputBlinkData.setVisible(false);
+               add(this.panelOutputPwmData = new OutputPwmPanel(this.center, panelPinData));
+               this.panelOutputPwmData.setVisible(true);
+               add(this.panelInputData = new InputPanel(this.center, panelPinData));
+               this.panelInputData.setVisible(false);
+               add(this.panelInputPwmData = new InputPwmPanel(this.center, panelPinData));
+               this.panelInputPwmData.setVisible(false);
        }
 }
 
@@ -92,7 +125,7 @@ class GPIOTable extends JPanel implements ActionListener {
                        new JLabel(I18N.tr("Pin")), new JLabel(I18N.tr("Name")) };
 
        private final Object[][] cells;
-       PinData panelPinData;
+       PinPanel panelPinData;
        GPIOSettings gpioSettings;
 
        /**
@@ -101,45 +134,48 @@ class GPIOTable extends JPanel implements ActionListener {
        public GPIOTable(GPIOSettings parent) {
                super(new GridLayout(21, 4));
                this.gpioSettings = parent;
-               this.pinButtons[0] = new PinButton(0, I18N.tr("+3.3V"), null);
-               this.pinButtons[1] = new PinButton(1, I18N.tr("+5V"), null);
+               final String TEXT_GROUND = "GND";
+               final String TEXT_3V = "+3.3V";
+               final String TEXT_5V = "+5.0V";
+               this.pinButtons[0] = new PinButton(0, I18N.tr(TEXT_3V), null);
+               this.pinButtons[1] = new PinButton(1, I18N.tr(TEXT_5V), null);
                this.pinButtons[2] = new PinButton(2, "SDA1", PinNumber.PIN_RPi2_03);
-               this.pinButtons[3] = new PinButton(3, I18N.tr("+5V"), null);
+               this.pinButtons[3] = new PinButton(3, I18N.tr(TEXT_5V), null);
                this.pinButtons[4] = new PinButton(4, "SCL1", PinNumber.PIN_RPi2_05);
-               this.pinButtons[5] = new PinButton(5, I18N.tr("GND"), null);
+               this.pinButtons[5] = new PinButton(5, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[6] = new PinButton(6, "GCLK", PinNumber.PIN_RPi2_07);
                this.pinButtons[7] = new PinButton(7, "TXD0", PinNumber.PIN_RPi2_08);
-               this.pinButtons[8] = new PinButton(8, I18N.tr("GND"), null);
+               this.pinButtons[8] = new PinButton(8, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[9] = new PinButton(9, "RXD0", PinNumber.PIN_RPi2_10);
                this.pinButtons[10] = new PinButton(10, "GEN0", PinNumber.PIN_RPi2_11);
                this.pinButtons[11] = new PinButton(11, "GEN1", PinNumber.PIN_RPi2_12);
                this.pinButtons[12] = new PinButton(12, "GEN2", PinNumber.PIN_RPi2_13);
-               this.pinButtons[13] = new PinButton(13, I18N.tr("GND"), null);
+               this.pinButtons[13] = new PinButton(13, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[14] = new PinButton(14, "GEN3", PinNumber.PIN_RPi2_15);
                this.pinButtons[15] = new PinButton(15, "GEN4", PinNumber.PIN_RPi2_16);
-               this.pinButtons[16] = new PinButton(16, I18N.tr("+3.3V"), null);
+               this.pinButtons[16] = new PinButton(16, I18N.tr(TEXT_3V), null);
                this.pinButtons[17] = new PinButton(17, "GEN5", PinNumber.PIN_RPi2_18);
                this.pinButtons[18] = new PinButton(18, "SPI_MOSI", PinNumber.PIN_RPi2_19);
-               this.pinButtons[19] = new PinButton(19, I18N.tr("GND"), null);
+               this.pinButtons[19] = new PinButton(19, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[20] = new PinButton(20, "SPI_MISO", PinNumber.PIN_RPi2_21);
                this.pinButtons[21] = new PinButton(21, "GEN6", PinNumber.PIN_RPi2_22);
                this.pinButtons[22] = new PinButton(22, "SPI_SCLK", PinNumber.PIN_RPi2_23);
                this.pinButtons[23] = new PinButton(23, "SPI_CE0", PinNumber.PIN_RPi2_24);
-               this.pinButtons[24] = new PinButton(24, I18N.tr("GND"), null);
+               this.pinButtons[24] = new PinButton(24, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[25] = new PinButton(25, "SPI_CE1", PinNumber.PIN_RPi2_26);
                this.pinButtons[26] = new PinButton(26, "I2C_ID_SC", PinNumber.PIN_RPi2_27);
                this.pinButtons[27] = new PinButton(27, "I2C_ID_SD", PinNumber.PIN_RPi2_28);
                this.pinButtons[28] = new PinButton(28, null, PinNumber.PIN_RPi2_29);
-               this.pinButtons[29] = new PinButton(29, I18N.tr("GND"), null);
+               this.pinButtons[29] = new PinButton(29, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[30] = new PinButton(30, null, PinNumber.PIN_RPi2_31);
                this.pinButtons[31] = new PinButton(31, null, PinNumber.PIN_RPi2_32);
                this.pinButtons[32] = new PinButton(32, null, PinNumber.PIN_RPi2_33);
-               this.pinButtons[33] = new PinButton(33, I18N.tr("GND"), null);
+               this.pinButtons[33] = new PinButton(33, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[34] = new PinButton(34, null, PinNumber.PIN_RPi2_35);
                this.pinButtons[35] = new PinButton(35, null, PinNumber.PIN_RPi2_36);
                this.pinButtons[36] = new PinButton(36, null, PinNumber.PIN_RPi2_37);
                this.pinButtons[37] = new PinButton(37, null, PinNumber.PIN_RPi2_38);
-               this.pinButtons[38] = new PinButton(38, I18N.tr("GND"), null);
+               this.pinButtons[38] = new PinButton(38, I18N.tr(TEXT_GROUND), null);
                this.pinButtons[39] = new PinButton(39, null, PinNumber.PIN_RPi2_40);
                for (final PinButton pinButton : this.pinButtons) {
                        pinButton.addActionListener(this);
@@ -206,39 +242,125 @@ class GPIOTable extends JPanel implements ActionListener {
         * @param panelPinData
         *            the panelPinData to set
         */
-       public void setPanelPinData(PinData panelPinData) {
+       public void setPanelPinData(PinPanel panelPinData) {
                this.panelPinData = panelPinData;
        }
+
+       /**
+        * Updates the widgets related to a given pin button.
+        *
+        * @param button
+        *            the button which should be updated
+        */
+       public void updateButton(PinButton button) {
+               final int row = button.getIndex() / 2;
+               if (button.getIndex() % 2 == 0) {
+                       ((JLabel) this.cells[row][0]).setText(button.getNickname());
+               } else {
+                       ((JLabel) this.cells[row][3]).setText(button.getNickname());
+               }
+       }
+
 }
 
-class InputData extends JPanel {
+class InputPanel extends JPanel implements ActionListener {
        private static final long serialVersionUID = 1L;
-       // private final Logger logger = LoggerFactory.getLogger(OutputData.class);
+       private final Logger logger = LoggerFactory.getLogger(this.getClass());
        private JComboBox<String> comboChannel;
-       private JTextField textChannel;
+       private JTextField textInterval;
        private final ControlCenter center;
-       private final PinData pinData;
+       private final PinPanel pinData;
+       JButton buttonRun;
 
        /**
         * Constructor.
+        *
+        * @param center
+        *            the control center
+        * @param pinData
+        *            the parent panel
         */
-       public InputData(ControlCenter center, PinData pinData) {
-               super(new GridLayout(0, 2));
+       public InputPanel(ControlCenter center, PinPanel pinData) {
+               super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]"));
                this.center = center;
                this.pinData = pinData;
+               add(new JLabel(I18N.tr("Input parameters:")), "span 2, wrap");
+
                add(new JLabel(I18N.tr("Channel:")));
-               add(this.textChannel = new JTextField("0"));
+               add(this.comboChannel = new JComboBox<String>(), "wrap");
+               this.center.addChannelComboBox(this.comboChannel);
                add(new JLabel(I18N.tr("Interval:")));
-               add(this.textChannel = new JTextField("0"));
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
-               add(new JLabel());
+               add(this.textInterval = new JTextField("0"), "wrap");
+               add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2, wrap");
+               this.buttonRun.addActionListener(this);
        }
+
+       /*
+        *
+        */
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == this.buttonRun) {
+                       final int interval = GuiUtils.numberOf(this.textInterval, 1000, true);
+                       final int channel = this.comboChannel.getSelectedIndex();
+                       this.logger.info(String.format("channel: %d interval: %d", channel, interval));
+               }
+       }
+
+}
+
+/**
+ * Manages the panel for input with Pulse Width Modulation.
+ *
+ * @author hm
+ *
+ */
+class InputPwmPanel extends JPanel implements ActionListener {
+       private static final long serialVersionUID = 1L;
+       private final Logger logger = LoggerFactory.getLogger(this.getClass());
+       private JComboBox<String> comboChannel;
+       private JTextField textFieldClock;
+       private JTextField textFieldSteps;
+       private final ControlCenter center;
+       private final PinPanel pinData;
+       private JButton buttonRun;
+
+       /**
+        * Constructor.
+        *
+        * @param center
+        *            the control center
+        * @param pinData
+        *            the parent panel
+        */
+       public InputPwmPanel(ControlCenter center, PinPanel pinData) {
+               super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]"));
+               this.center = center;
+               this.pinData = pinData;
+               add(new JLabel(I18N.tr("Input parameters:")), "span 2, wrap");
+
+               add(new JLabel(I18N.tr("Channel:")));
+               add(this.comboChannel = new JComboBox<String>(), "wrap");
+               this.center.addChannelComboBox(this.comboChannel);
+               add(new JLabel(I18N.tr("Clock:")));
+               add(this.textFieldClock = new JTextField("1000"), "wrap");
+               add(new JLabel(I18N.tr("Period:")));
+               add(this.textFieldSteps = new JTextField("1024"), "wrap");
+               add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2, wrap");
+               this.buttonRun.addActionListener(this);
+
+       }
+
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == this.buttonRun) {
+                       final int channel = this.comboChannel.getSelectedIndex();
+                       final int clock = GuiUtils.numberOf(this.textFieldClock, 1000, true);
+                       final int steps = GuiUtils.numberOf(this.textFieldSteps, 1024, true);
+                       this.logger.info(String.format("channel: %d clock: %d steps: %d", channel, clock, steps));
+               }
+       }
+
 }
 
 /**
@@ -247,12 +369,11 @@ class InputData extends JPanel {
  * @author hm
  *
  */
-class OutputData extends JPanel implements ActionListener {
+class OutputBlinkPanel extends JPanel implements ActionListener {
        /**
         *
         */
        private static final long serialVersionUID = 1L;
-       private final static String[] modes = { I18N.tr("Blink") };
        // private final Logger logger = LoggerFactory.getLogger(OutputData.class);
        private JComboBox<String> comboMode;
        private JTextField textCount;
@@ -260,26 +381,23 @@ class OutputData extends JPanel implements ActionListener {
        private JTextField textLow;
        private JButton buttonRun;
        private final ControlCenter center;
-       private final PinData pinData;
+       private final PinPanel pinData;
 
        /**
         * Constructor.
         */
-       public OutputData(ControlCenter center, PinData pinData) {
-               super(new GridLayout(0, 2));
+       public OutputBlinkPanel(ControlCenter center, PinPanel pinData) {
+               super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]"));
                this.center = center;
                this.pinData = pinData;
-               add(new JLabel(I18N.tr("Mode:")));
-               add(this.comboMode = new JComboBox<>(OutputData.modes));
+               add(new JLabel(I18N.tr("Blink parameters:")), "span 2, wrap");
                add(new JLabel(I18N.tr("Count:")));
-               add(this.textCount = new JTextField("10"));
+               add(this.textCount = new JTextField("10"), "wrap");
                add(new JLabel(I18N.tr("High:")));
-               add(this.textHigh = new JTextField("500"));
+               add(this.textHigh = new JTextField("500"), "wrap");
                add(new JLabel(I18N.tr("Low:")));
-               add(this.textLow = new JTextField("500"));
-               add(new JLabel(""));
-               add(this.buttonRun = new JButton(I18N.tr("Start")));
-               this.comboMode.addActionListener(this);
+               add(this.textLow = new JTextField("500"), "wrap");
+               add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2");
                this.buttonRun.addActionListener(this);
        }
 
@@ -294,44 +412,116 @@ class OutputData extends JPanel implements ActionListener {
                if (e.getSource() == this.buttonRun) {
                        final GpioClient client = this.center.getClient();
                        final PinButton current = this.pinData.getCurrentPin();
-                       final int count = numberOf(this.textCount, 10);
-                       final int high = numberOf(this.textHigh, 500);
-                       final int low = numberOf(this.textLow, 500);
+                       final int count = GuiUtils.numberOf(this.textCount, 10, true);
+                       final int high = GuiUtils.numberOf(this.textHigh, 500, true);
+                       final int low = GuiUtils.numberOf(this.textLow, 500, true);
                        this.center.say(Announcer.LOG,
-                                       String.format(I18N.tr("blinking started on pin %d"), current.getPinNumber()));
+                                       String.format(I18N.tr("blinking started on pin %d"), current.getPinNumber().getNumber()));
                        final DynBytes answer = client.blink(current.getPinNumber(), count, high, low);
                        if (answer.startsWith("OK")) {
                                this.center.say(Announcer.LOG,
-                                               String.format(I18N.tr("pin %d blinks %d times"), current.getPinNumber(), count));
+                                               String.format(I18N.tr("pin %d blinks %d times"), current.getPinNumber().getNumber(), count));
                        }
                } else if (e.getSource() == this.comboMode) {
 
                }
        }
+}
+
+/**
+ * Manages the panel for output with Pulse Width Modulation.
+ *
+ * @author hm
+ *
+ */
+class OutputPwmPanel extends JPanel implements ActionListener {
+       /**
+        *
+        */
+       private static final long serialVersionUID = 1L;
+       // private final Logger logger = LoggerFactory.getLogger(OutputData.class);
+       static String[] modes = { I18N.tr("constant"), I18N.tr("sin"), I18N.tr("falling slope"), I18N.tr("rising slope"),
+                       I18N.tr("dual slope") };
+       private JComboBox<String> comboMode;
+       private JTextField textFieldPeriod;
+       private JTextField textFieldStartValue;
+       private JTextField textFieldFunctionSteps;
+       private JTextField textFieldCount;
+       private JButton buttonRun;
+       private final ControlCenter center;
+       private final PinPanel pinData;
 
        /**
-        * Gets a number from a text field.
+        * Constructor.
         *
-        * @param text
-        *            the text field
-        * @param defaultValue
-        *            the return value for invalid field values
-        * @return <i>defaultValue</i>: invalid field value<br>
-        *         otherwise: the value of the field as integer
-        */
-       private int numberOf(JTextField text, int defaultValue) {
-               int rc = defaultValue;
-               try {
-                       rc = Integer.parseInt(text.getText());
-               } catch (final NumberFormatException e) {
-                       this.center.say(Announcer.ERROR,
-                                       String.format(I18N.tr("not a number: '%s' used instead: %d"), text.getText(), rc));
-                       text.setText(String.valueOf(rc));
+        * @param center
+        *            the control center
+        * @param pinData
+        *            the parent panel
+        */
+       public OutputPwmPanel(ControlCenter center, PinPanel pinData) {
+               super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]"));
+               this.center = center;
+               this.pinData = pinData;
+               add(new JLabel(I18N.tr("Output Parameters:")), "span 2, wrap");
+               add(new JLabel(I18N.tr("Clock:")));
+               add(this.textFieldPeriod = new JTextField("10000"), "wrap");
+               this.textFieldPeriod.setToolTipText(I18N.tr("Duration of the pwm signal in microseconds"));
+               add(new JLabel(I18N.tr("Start value:")));
+               add(this.textFieldStartValue = new JTextField("0"), "wrap");
+               this.textFieldStartValue.setToolTipText(I18N.tr("First pwm value, multiplied with 1E4, e.g. 223 means 0.0223"));
+               add(new JLabel(I18N.tr("Function:")));
+               add(this.comboMode = new JComboBox<String>(OutputPwmPanel.modes), "wrap");
+               this.comboMode.setToolTipText(I18N.tr("A periodic function defining the pwm values."));
+               add(new JLabel(I18N.tr("Function steps:")));
+               add(this.textFieldFunctionSteps = new JTextField("20"), "wrap");
+               this.textFieldFunctionSteps.setToolTipText(I18N
+                               .tr("Count of pwm periods building a 'function period': After this time the pwm values are repeated."));
+               add(new JLabel(I18N.tr("Count:")));
+               add(this.textFieldCount = new JTextField("2147483647"), "wrap");
+
+               add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2");
+               this.buttonRun.addActionListener(this);
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see
+        * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+        */
+       @Override
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == this.buttonRun) {
+                       final GpioClient client = this.center.getClient();
+                       final PinButton current = this.pinData.getCurrentPin();
+                       final int period = GuiUtils.numberOf(this.textFieldPeriod, 10000, true);
+                       final double startValue = GuiUtils.numberOf(this.textFieldStartValue, 0, true) / 1E4;
+                       final int mode = this.comboMode.getSelectedIndex();
+                       final Function function = Function.findById(mode);
+                       final int functionSteps = GuiUtils.numberOf(this.textFieldFunctionSteps, 0, true);
+                       final int count = GuiUtils.numberOf(this.textFieldCount, 0, true);
+
+                       this.center.say(Announcer.LOG,
+                                       String.format(I18N.tr("PWM output: period: %d start: %f Mode: %s"), period, startValue, mode));
+                       final DynBytes answer = client.pwmOutput(current.getPinNumber(), period, startValue, function,
+                                       functionSteps, count);
+                       if (answer.startsWith("OK")) {
+                               this.center.say(Announcer.LOG,
+                                               String.format(I18N.tr("pin %d puts pwm %d times"), current.getPinNumber().getNumber(), count));
+                       }
                }
-               return rc;
        }
 }
 
+/**
+ * Manages the properties of a GPIO pin button.
+ *
+ * This button can be used to set the properties of a pin.
+ *
+ * @author hm
+ *
+ */
 class PinButton extends JButton {
        /**
         *
@@ -339,12 +529,20 @@ class PinButton extends JButton {
        private static final long serialVersionUID = 1L;
        public static final int MODE_UNDEF = 0;
        public static final int MODE_INPUT = 1;
-       public static final int MODE_OUTPUT = 2;
+       public static final int MODE_INPUT_PWM = 2;
+       public static final int MODE_OUTPUT_BLINK = 3;
+       public static final int MODE_OUTPUT_PWM = 4;
+       public static final int MODE_FOCUS = 5;
+       public static final int MODE_COUNT = PinButton.MODE_FOCUS + 1;
+       static ImageIcon[] icons = null;
+       private final Logger logger = LoggerFactory.getLogger(PinButton.class);
+       private JLabel labelPin;
        private final PinNumber pinNumber;
        private final String title;
+       private String nickname;
        private int mode = PinButton.MODE_UNDEF;
 
-       int index;
+       private final int index;
 
        /**
         * Constructor.
@@ -358,10 +556,36 @@ class PinButton extends JButton {
         *            otherwise: the pin number
         */
        public PinButton(int index, String title, PinNumber pinNumber) {
-               super(pinNumber == null ? title : String.valueOf(index + 1));
+               super(pinNumber == null ? title : String.format("P%02d", index + 1));
+               if (PinButton.icons == null) {
+                       PinButton.icons = new ImageIcon[PinButton.MODE_COUNT];
+                       PinButton.icons[PinButton.MODE_UNDEF] = null;
+                       PinButton.icons[PinButton.MODE_INPUT] = null;
+                       PinButton.icons[PinButton.MODE_INPUT_PWM] = null;
+                       PinButton.icons[PinButton.MODE_OUTPUT_BLINK] = null;
+                       PinButton.icons[PinButton.MODE_OUTPUT_PWM] = null;
+                       try {
+                               final URL url = getClass().getResource("/icons/input16.png");
+                               final Image image = url == null ? null : ImageIO.read(url);
+                               ImageIcon icon = image == null ? null : new ImageIcon(image);
+                               PinButton.icons[PinButton.MODE_INPUT] = icon;
+                               PinButton.icons[PinButton.MODE_INPUT_PWM] = icon;
+                               icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/output16.png")));
+                               PinButton.icons[PinButton.MODE_OUTPUT_BLINK] = icon;
+                               PinButton.icons[PinButton.MODE_OUTPUT_PWM] = icon;
+                               icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/undef16.png")));
+                               PinButton.icons[PinButton.MODE_UNDEF] = icon;
+                               icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/focus16.png")));
+                               PinButton.icons[PinButton.MODE_FOCUS] = icon;
+                       } catch (final IOException e) {
+                               this.logger.error("cannot load icon", e);
+                       }
+
+               }
                this.index = index;
                this.title = title == null ? pinNumber.getName() : title;
                this.pinNumber = pinNumber;
+               setIcon(PinButton.icons[this.mode]);
        }
 
        /**
@@ -378,6 +602,13 @@ class PinButton extends JButton {
                return this.mode;
        }
 
+       /**
+        * @return the nickname
+        */
+       public String getNickname() {
+               return this.nickname == null || this.nickname.isEmpty() ? this.title : this.nickname;
+       }
+
        /**
         * @return the pinNumber
         */
@@ -397,7 +628,18 @@ class PinButton extends JButton {
         *            the mode to set
         */
        public void setMode(int mode) {
-               this.mode = mode;
+               if (mode != PinButton.MODE_FOCUS) {
+                       this.mode = mode;
+               }
+               setIcon(PinButton.icons[this.mode]);
+       }
+
+       /**
+        * @param nickname
+        *            the nickname to set
+        */
+       public void setNickname(String nickname) {
+               this.nickname = nickname;
        }
 }
 
@@ -407,17 +649,19 @@ class PinButton extends JButton {
  * @author hm
  *
  */
-class PinData extends JPanel implements ActionListener {
+class PinPanel extends JPanel implements ActionListener, FocusListener {
        /**
         *
         */
        private static final long serialVersionUID = 1L;
-       private final Logger logger = LoggerFactory.getLogger(PinData.class);
+       private final Logger logger = LoggerFactory.getLogger(PinPanel.class);
        private JLabel labelPin;
        private JLabel labelInternalNo;
        private JLabel labelTitle;
+       private JTextField textFieldName;
        private JComboBox<String> comboMode;
-       private final String[] modes = { I18N.tr("undefined"), I18N.tr("input"), I18N.tr("output") };
+       private final String[] modes = { I18N.tr("undefined"), I18N.tr("input"), I18N.tr("input-pwm"),
+                       I18N.tr("output-blink"), I18N.tr("output-pwm") };
        private PinButton currentPin = null;
        private final GPIOTable parent;
 
@@ -427,15 +671,19 @@ class PinData extends JPanel implements ActionListener {
         * @param table
         *            info about the pins
         */
-       public PinData(GPIOTable table) {
-               super(new GridLayout(0, 2));
+       public PinPanel(GPIOTable table) {
+               super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]"));
                this.parent = table;
-               add(new JLabel(I18N.tr("Pin No:")));
-               add(this.labelPin = new JLabel());
-               add(new JLabel(I18N.tr("GPIO number:")));
-               add(this.labelInternalNo = new JLabel());
+               add(new JLabel(I18N.tr("Pin description:")), "span 2, wrap");
+               add(new JLabel(I18N.tr("Pin no:")));
+               add(this.labelPin = new JLabel(), "wrap");
+               add(new JLabel(I18N.tr("GPIO id:")));
+               add(this.labelInternalNo = new JLabel(), "wrap");
                add(new JLabel(I18N.tr("Title:")));
-               add(this.labelTitle = new JLabel());
+               add(this.labelTitle = new JLabel(), "wrap");
+               add(new JLabel(I18N.tr("Name:")));
+               add(this.textFieldName = new JTextField(), "wrap");
+               this.textFieldName.addFocusListener(this);
                add(new JLabel(I18N.tr("Mode:")));
                add(this.comboMode = new JComboBox<>(this.modes));
                this.comboMode.addActionListener(this);
@@ -454,10 +702,15 @@ class PinData extends JPanel implements ActionListener {
                if (event.getSource() == this.comboMode) {
                        final int index = this.comboMode.getSelectedIndex();
                        if (this.currentPin.getMode() != index) {
-                               this.logger.debug("actionPerformed(); changing mode: " + index);
+                               final boolean isOutputPWM = index == PinButton.MODE_OUTPUT_PWM;
+                               this.logger.debug(
+                                               "actionPerformed(); changing mode: " + index + " isOutPwm: " + String.valueOf(isOutputPWM));
                                this.currentPin.setMode(index);
-                               this.parent.getGpioSettings().getPanelOutputData().setVisible(index == PinButton.MODE_OUTPUT);
+                               this.parent.getGpioSettings().getPanelOutputBlinkData()
+                                               .setVisible(index == PinButton.MODE_OUTPUT_BLINK);
+                               this.parent.getGpioSettings().getPanelOutputPwmData().setVisible(isOutputPWM);
                                this.parent.getGpioSettings().getPanelInputData().setVisible(index == PinButton.MODE_INPUT);
+                               this.parent.getGpioSettings().getPanelInputPwmData().setVisible(index == PinButton.MODE_INPUT_PWM);
                        }
                }
 
@@ -470,21 +723,57 @@ class PinData extends JPanel implements ActionListener {
         *            properties of the pin
         */
        public void fillData(PinButton button) {
+               if (this.currentPin != null) {
+                       // change the icon:
+                       this.currentPin.setMode(this.currentPin.getMode());
+               }
                this.currentPin = button;
                final PinNumber number = button.getPinNumber();
                this.labelPin.setText(String.valueOf(button.getIndex() + 1));
                this.labelInternalNo.setText(number == null ? "-" : String.valueOf(number.getNumber()));
                this.labelTitle.setText(button.getTitle());
                this.comboMode.setVisible(number != null);
-               final int index = button.getMode();
-               this.comboMode.setSelectedIndex(index);
-               final OutputData output = this.parent.getGpioSettings().getPanelOutputData();
+               final int mode = button.getMode();
+               this.comboMode.setSelectedIndex(mode);
+               final OutputBlinkPanel output = this.parent.getGpioSettings().getPanelOutputBlinkData();
                if (output != null) {
-                       output.setVisible(index == PinButton.MODE_OUTPUT);
+                       output.setVisible(mode == PinButton.MODE_OUTPUT_BLINK);
+               }
+               final OutputPwmPanel outputPwm = this.parent.getGpioSettings().getPanelOutputPwmData();
+               if (outputPwm != null) {
+                       outputPwm.setVisible(mode == PinButton.MODE_OUTPUT_PWM);
                }
-               final InputData input = this.parent.getGpioSettings().getPanelInputData();
+               final InputPanel input = this.parent.getGpioSettings().getPanelInputData();
                if (input != null) {
-                       input.setVisible(index == PinButton.MODE_INPUT);
+                       input.setVisible(mode == PinButton.MODE_INPUT);
+               }
+               final InputPwmPanel inputPwm = this.parent.getGpioSettings().getPanelInputPwmData();
+               if (inputPwm != null) {
+                       inputPwm.setVisible(mode == PinButton.MODE_INPUT);
+               }
+               button.setMode(PinButton.MODE_FOCUS);
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
+        */
+       @Override
+       public void focusGained(FocusEvent e) {
+       }
+
+       /*
+        * (non-Javadoc)
+        *
+        * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
+        */
+       @Override
+       public void focusLost(FocusEvent e) {
+               if (e.getSource() == this.textFieldName && this.currentPin != null) {
+                       final String nickname = this.textFieldName.getText();
+                       this.currentPin.setNickname(nickname);
+                       this.parent.updateButton(this.currentPin);
                }
        }
 
index 26b2aa1db00ca12cbb6b4da857ac30d70aa8441b..84f2d10a59c0a6a1403334f6383b5e9de940d9b5 100644 (file)
@@ -83,4 +83,33 @@ public class StringUtils {
                }
                return rc;
        }
+
+       /**
+        * Evaluate a arithmetic expression.
+        *
+        * @param expr
+        *            a formula with '+', '-', '*', '/', "**" and numbers but no
+        *            parentheses
+        * @return the result
+        */
+       long numberOf(final String expr) throws Exception {
+               long rc = 0;
+               final String[] terms = expr.split("[-+]");
+               if (terms.length == 1) {
+                       rc = Long.parseLong(expr);
+               } else {
+                       for (final String term : terms) {
+                               final String[] factors = term.split("[*/]");
+                               if (factors.length == 1) {
+
+                               } else {
+                                       for (final String factor : factors) {
+                                               final long rc2 = 1;
+                                               final String[] parts = factor.split("\\*\\*");
+                                       }
+                               }
+                       }
+               }
+               return rc;
+       }
 }
diff --git a/src/main/resources/icons/focus16.png b/src/main/resources/icons/focus16.png
new file mode 100644 (file)
index 0000000..54a21d7
Binary files /dev/null and b/src/main/resources/icons/focus16.png differ
diff --git a/src/main/resources/icons/input16.png b/src/main/resources/icons/input16.png
new file mode 100644 (file)
index 0000000..4055e33
Binary files /dev/null and b/src/main/resources/icons/input16.png differ
diff --git a/src/main/resources/icons/output16.png b/src/main/resources/icons/output16.png
new file mode 100644 (file)
index 0000000..4218f22
Binary files /dev/null and b/src/main/resources/icons/output16.png differ
diff --git a/src/main/resources/icons/undef16.png b/src/main/resources/icons/undef16.png
new file mode 100644 (file)
index 0000000..07f7b0e
Binary files /dev/null and b/src/main/resources/icons/undef16.png differ
diff --git a/src/test/java/de/republib/expr/ExpressionParserTest.java b/src/test/java/de/republib/expr/ExpressionParserTest.java
new file mode 100644 (file)
index 0000000..5df0fce
--- /dev/null
@@ -0,0 +1,58 @@
+package de.republib.expr;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ExpressionParserTest {
+
+       @Test
+       public void shouldConstruct() throws ParserException {
+               final Expression expr = new Expression("1");
+               final Variant result = expr.expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 1L);
+       }
+
+       @Test
+       public void shouldFindVariable() {
+               final Expression expr = new Expression("");
+               final String name = "aLong";
+               final Variable var = expr.findVariable(name);
+               var.assign(new Variant(47L));
+               Assert.assertEquals(expr.findVariable(name).getValue().getType(), DataType.LONG);
+               Assert.assertEquals(expr.findVariable(name).getValue().getLongValue(), 47L);
+       }
+
+       @Test
+       public void shouldWorkExpr() throws ParserException {
+               final Expression expr = new Expression("1 + 2*3");
+               Variant result = expr.expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 163L);
+
+               result = expr.reset("2 + 2*3**4 - 15 / 3 % 4").expr();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 163L);
+       }
+
+       @Test
+       public void shouldWorkTerm() throws ParserException {
+               final Expression expr = new Expression("2");
+
+               Variant result = expr.term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 2L);
+
+               result = expr.reset("-2").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), -2L);
+
+               result = expr.reset("'x'").term();
+               Assert.assertEquals(result.getType(), DataType.STRING);
+               Assert.assertEquals(result.getStringValue(), "x");
+
+               result = expr.reset("(44)").term();
+               Assert.assertEquals(result.getType(), DataType.LONG);
+               Assert.assertEquals(result.getLongValue(), 44L);
+       }
+}
diff --git a/src/test/java/de/republib/expr/ScannerTest.java b/src/test/java/de/republib/expr/ScannerTest.java
new file mode 100644 (file)
index 0000000..6ebab9f
--- /dev/null
@@ -0,0 +1,274 @@
+package de.republib.expr;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import de.republib.expr.ParserException;
+import de.republib.expr.Scanner;
+import de.republib.expr.Token;
+import de.republib.expr.TokenType;
+
+public class ScannerTest {
+       @Test
+       public void shouldConstruct() {
+               final Scanner scanner = new Scanner("");
+               Assert.assertNotNull(scanner);
+       }
+
+       @Test
+       public void shouldScanEndOfString() throws ParserException {
+               final Scanner scanner = new Scanner("");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(TokenType.END_OF_STRING, token.getType());
+
+               scanner.reset("1");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset("a");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.ID);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset("'x");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset("\"");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset("*");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset("**");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+
+               scanner.reset(" ");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.END_OF_STRING);
+       }
+
+       @Test
+       public void shouldScanId() throws ParserException {
+               final Scanner scanner = new Scanner("x\tboy007\"a\"");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.ID);
+               Assert.assertEquals(token.getString(), "x");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getString(), "boy007");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+       }
+
+       @Test
+       public void shouldScanInteger() throws ParserException {
+               final Scanner scanner = new Scanner("9944  1\n'7733'");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+               Assert.assertEquals(token.getLongValue(), 9944);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+               Assert.assertEquals(token.getLongValue(), 1);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+
+               token = scanner.reset("0x1234567890 0Xabcdef1 0xABCDEF0").nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+               Assert.assertEquals(token.getLongValue(), 0x1234567890L);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+               Assert.assertEquals(token.getLongValue(), 0xabcdef1L);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.INTEGER);
+               Assert.assertEquals(token.getLongValue(), 0xABCDEF0L);
+       }
+
+       @Test
+       public void shouldScanOp() throws ParserException {
+               final Scanner scanner = new Scanner("+ - * / % ** ( )");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.PLUS);
+               Assert.assertEquals(token.getString(), "+");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.MINUS);
+               Assert.assertEquals(token.getString(), "-");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.TIMES);
+               Assert.assertEquals(token.getString(), "*");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.DIV);
+               Assert.assertEquals(token.getString(), "/");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.MOD);
+               Assert.assertEquals(token.getString(), "%");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.POWER);
+               Assert.assertEquals(token.getString(), "**");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.LEFT_PARENTH);
+               Assert.assertEquals(token.getString(), "(");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.OP);
+               Assert.assertEquals(token.getOpCode(), OpCode.RIGHT_PARENT);
+               Assert.assertEquals(token.getString(), ")");
+       }
+
+       @Test
+       public void shouldScanSpaces() throws ParserException {
+               final Scanner scanner = new Scanner("\n\r\f \tX\t\r\n ");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+               Assert.assertEquals(token.getString(), "\n\r\f \t");
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.ID);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+               Assert.assertEquals(token.getString(), "\t\r\n ");
+       }
+
+       @Test
+       public void shouldScanString() throws ParserException {
+               final Scanner scanner = new Scanner("'a'\"xy\"   '\\b\\n\\r' '\\\\\\'\\\"\\f'");
+               Token token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "a");
+               Assert.assertEquals(token.getRawString(), "'a'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "xy");
+               Assert.assertEquals(token.getRawString(), "\"xy\"");
+               Assert.assertEquals(token.getDelimiter(), '\"');
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "\b\n\r");
+               Assert.assertEquals(token.getRawString(), "'\\b\\n\\r'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "\\'\"\f");
+               Assert.assertEquals(token.getRawString(), "'\\\\\\'\\\"\\f'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               scanner.reset("'\\xa.'");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "\n.");
+               Assert.assertEquals(token.getRawString(), "'\\xa.'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               scanner.reset("'\\x42c\\xd.\\x20'");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "Bc\r. ");
+               Assert.assertEquals(token.getRawString(), "'\\x42c\\xd.\\x20'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               scanner.reset("'\\u12ab' '\\u45'");
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "\u12ab");
+               Assert.assertEquals(token.getRawString(), "'\\u12ab'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.SPACE);
+
+               token = scanner.nextToken();
+               Assert.assertEquals(token.getType(), TokenType.STRING);
+               Assert.assertEquals(token.getString(), "\u0045");
+               Assert.assertEquals(token.getRawString(), "'\\u45'");
+               Assert.assertEquals(token.getDelimiter(), '\'');
+       }
+
+}
diff --git a/src/test/java/de/republib/expr/VariableTest.java b/src/test/java/de/republib/expr/VariableTest.java
new file mode 100644 (file)
index 0000000..4702f8e
--- /dev/null
@@ -0,0 +1,25 @@
+package de.republib.expr;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class VariableTest {
+
+       @Test
+       public void shouldAssign() {
+               final Variable var = new Variable("v1");
+               var.assign(new Variant(100L));
+               Assert.assertEquals(var.getValue().getType(), DataType.LONG);
+               Assert.assertEquals(var.getValue().getLongValue(), 100L);
+
+               var.assign(new Variant("Hi"));
+               Assert.assertEquals(var.getValue().getType(), DataType.STRING);
+               Assert.assertEquals(var.getValue().getStringValue(), "Hi");
+       }
+
+       @Test
+       public void ShouldConstruct() {
+               final Variable var = new Variable("v1");
+               Assert.assertEquals(var.getId(), "v1");
+       }
+}
diff --git a/src/test/java/de/republib/expr/VariantTest.java b/src/test/java/de/republib/expr/VariantTest.java
new file mode 100644 (file)
index 0000000..d7880be
--- /dev/null
@@ -0,0 +1,226 @@
+package de.republib.expr;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class VariantTest extends Variant {
+       VariantTest() {
+               super("Test");
+       }
+
+       @Test
+       public void shouldConstruct() {
+               final Variant varLong = new Variant(477L);
+               Assert.assertEquals(varLong.getLongValue(), 477L);
+
+               final Variant varBool = new Variant(true);
+               Assert.assertTrue(varBool.getBoolValue());
+
+               final Variant varFloat = new Variant(1.33E-99);
+               Assert.assertEquals(varFloat.getDoubleValue(), 1.33E-99);
+
+               final Variant varString = new Variant("Hi!");
+               Assert.assertEquals(varString.getStringValue(), "Hi!");
+       }
+
+       @Test
+       public void shouldOperateWithLong() throws VariantException {
+               final Variant var1 = new Variant(7L);
+               final Variant var2 = new Variant(4L);
+
+               var1.binaryOperation(OpCode.PLUS, var2);
+               Assert.assertEquals(var1.getLongValue(), 11L);
+
+               var1.binaryOperation(OpCode.MINUS, var2);
+               Assert.assertEquals(var1.getLongValue(), 7L);
+
+               var1.binaryOperation(OpCode.TIMES, var2);
+               Assert.assertEquals(var1.getLongValue(), 28L);
+
+               var1.binaryOperation(OpCode.DIV, var2);
+               Assert.assertEquals(var1.getLongValue(), 7L);
+
+               var1.binaryOperation(OpCode.MOD, var2);
+               Assert.assertEquals(var1.getLongValue(), 3L);
+
+               var1.binaryOperation(OpCode.POWER, var2);
+               Assert.assertEquals(var1.getLongValue(), 81L);
+       }
+
+       /**
+        * Change the data type to boolean.
+        *
+        * @param value
+        *            the boolean value
+        * @return the instance (for chaining)
+        */
+
+       @Test
+       public void shouldOperateWithString() throws VariantException {
+               final Variant var1 = new Variant("Abc");
+               final Variant var2 = new Variant("...ZZZ");
+               final Variant var3 = new Variant(3L);
+               var1.binaryOperation(OpCode.TIMES, var3);
+               Assert.assertEquals(var1.getStringValue(), "AbcAbcAbc");
+
+               var1.binaryOperation(OpCode.PLUS, var2);
+               Assert.assertEquals(var1.getStringValue(), "AbcAbcAbc...ZZZ");
+
+       }
+
+       @Test
+       public void shouldRedefine() {
+               final Variant var1 = new Variant(477L);
+               Assert.assertTrue(var1.isType(DataType.LONG));
+               Assert.assertEquals(var1.getLongValue(), 477L);
+
+               var1.redefineBool(true);
+               Assert.assertTrue(var1.isType(DataType.BOOLEAN));
+               Assert.assertTrue(var1.getBoolValue());
+
+               var1.redefineString("Hi");
+               Assert.assertTrue(var1.isType(DataType.STRING));
+               Assert.assertEquals(var1.getStringValue(), "Hi");
+
+               var1.redefineDouble(7.33E-2);
+               Assert.assertTrue(var1.isType(DataType.DOUBLE));
+               Assert.assertEquals(var1.getDoubleValue(), 7.33E-2);
+
+               final Object obj = new Integer(22);
+               var1.redefineObject(obj);
+               Assert.assertTrue(var1.isType(DataType.OBJECT));
+               Assert.assertEquals(var1.getObjectValue(), obj);
+       }
+
+       @Test
+       public void shouldRedefineByCopy() {
+               final Variant var1 = new Variant(477L);
+               final Variant var2 = new Variant("Wow");
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.STRING));
+               Assert.assertEquals(var1.asString(), "Wow");
+
+               var2.redefineBool(false);
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.BOOLEAN));
+               Assert.assertEquals(var1.asString(), "false");
+
+               var2.redefineDouble(1E10);
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.DOUBLE));
+               Assert.assertEquals(var1.asString(), "1.0E10");
+
+               var2.redefineLong(27L);
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.LONG));
+               Assert.assertEquals(var1.asString(), "27");
+
+               final Object obj = new String("do it soon");
+               var2.redefineObject(obj);
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.OBJECT));
+               Assert.assertEquals(var1.asString(), obj);
+
+               final Variable var = new Variable("dummy");
+               var2.redefineVariable(var);
+               var1.redefine(var2);
+               Assert.assertTrue(var1.isType(DataType.VARIABLE));
+               Assert.assertEquals(var1.getVariableValue(), var);
+               Assert.assertEquals(var1.getVariableValue().getValue().getStringValue(), var.getValue().getStringValue());
+       }
+
+       @Test
+       public void shouldThrowExceptionWithTypesAndOp() {
+               final Variant varLong = new Variant(477L);
+               redefineString("");
+               try {
+                       wrongDataType(this, OpCode.POWER, varLong);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf(DataType.LONG.name()) > 0);
+                       Assert.assertTrue(e.getMessage().indexOf(DataType.STRING.name()) > 0);
+                       Assert.assertTrue(e.getMessage().indexOf(OpCode.POWER.name()) > 0);
+               }
+       }
+
+       @Test
+       public void shouldThrowOnDivBy0() {
+               try {
+                       final Variant var1 = new Variant(-7L);
+                       final Variant var2 = new Variant(0L);
+                       var1.binaryOperation(OpCode.DIV, var2);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf("0") > 0);
+               }
+       }
+
+       @Test
+       public void shouldThrowOnPower() {
+               try {
+                       final Variant var1 = new Variant(2L);
+                       final Variant var2 = new Variant(-1L);
+                       var1.binaryOperation(OpCode.POWER, var2);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf("<") > 0);
+               }
+               try {
+                       final Variant var1 = new Variant(2L);
+                       final Variant var2 = new Variant(1024 * 1024);
+                       var1.binaryOperation(OpCode.POWER, var2);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf(String.valueOf(1024 * 1024)) > 0);
+                       Assert.assertFalse(e.getMessage().indexOf("<") > 0);
+               }
+       }
+
+       @Test
+       public void shouldThrowOnTimesForString() {
+               try {
+                       final Variant var1 = new Variant("x");
+                       final Variant var2 = new Variant(1024L * 1024);
+                       var1.binaryOperation(OpCode.TIMES, var2);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf(String.valueOf(1024 * 1024)) > 0);
+               }
+       }
+
+       @Test
+       public void shouldThrowOnUnexpectedOp() {
+               final Variant var1 = new Variant("Abc");
+               final Variant var2 = new Variant("...ZZZ");
+               try {
+                       var1.binaryOperation(OpCode.UNDEF, var2);
+               } catch (final VariantException e) {
+                       Assert.assertTrue(e.getMessage().indexOf(OpCode.UNDEF.name()) > 0);
+               }
+       }
+
+       @Test
+       public void shouldWorkasLong() throws VariantException {
+               final Variant varLong = new Variant(477L);
+               Assert.assertEquals(varLong.asLong(-3), 477L);
+
+               final Variant varBool = new Variant(true);
+               Assert.assertEquals(varBool.asLong(5), 1L);
+
+               final Variant varFloat = new Variant(1.33E2);
+               Assert.assertEquals(varFloat.asLong(99), 133L);
+
+               final Variant varString = new Variant("744 x");
+               Assert.assertEquals(varString.asLong(44), 744L);
+       }
+
+       @Test
+       public void shouldWorkAsString() {
+               final Variant varLong = new Variant(477L);
+               Assert.assertEquals(varLong.asString(), "477");
+
+               final Variant varBool = new Variant(true);
+               Assert.assertEquals(varBool.asString(), "true");
+
+               final Variant varFloat = new Variant(1.33E-99);
+               Assert.assertEquals(varFloat.asString(), "1.33E-99");
+
+               final Variant varString = new Variant("Hi!");
+               Assert.assertEquals(varString.asString(), "Hi!");
+       }
+}
diff --git a/src/test/java/de/republib/gui/GuiUtilsTest.java b/src/test/java/de/republib/gui/GuiUtilsTest.java
new file mode 100644 (file)
index 0000000..3fd8e21
--- /dev/null
@@ -0,0 +1,22 @@
+package de.republib.gui;
+
+import javax.swing.JTextField;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class GuiUtilsTest {
+
+       @Test
+       public void numberOf() {
+               final JTextField field = new JTextField();
+               field.setText("-4711");
+               Assert.assertEquals(GuiUtils.numberOf(field, -1, true), -4711);
+               field.setText("");
+               Assert.assertEquals(GuiUtils.numberOf(field, -1, true), -1);
+               Assert.assertEquals(field.getText(), "-1");
+               field.setText("100k");
+               Assert.assertEquals(GuiUtils.numberOf(field, -44, false), -44);
+               Assert.assertEquals(field.getText(), "100k");
+       }
+}