From 08b8d5a7eb5d2bbf0a1b85d761a0781999f7f060 Mon Sep 17 00:00:00 2001 From: gumartinm Date: Mon, 30 Apr 2012 09:24:26 +0200 Subject: [PATCH] Working on my JavaPOS keyboard driver. No time for comments... --- .../java/de/javapos/example/MyPOSKeyboard.java | 1 + .../java/de/javapos/example/ThreadFireEvent.java | 34 ++++++-- .../main/java/de/javapos/example/ThreadGate.java | 34 ++++++++ .../main/java/de/javapos/example/ThreadSafe.java | 13 --- .../de/javapos/example/annotation/GuardedBy.java | 38 +++++++++ .../de/javapos/example/annotation/ThreadSafe.java | 22 +++++ .../example/hardware/KeyBoardDeviceLinux.java | 99 ++++++++++++++++------ .../javapos/example/queue/JposEventQueueImpl.java | 14 ++- 8 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadGate.java delete mode 100644 JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadSafe.java create mode 100644 JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/GuardedBy.java create mode 100644 JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/ThreadSafe.java diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/MyPOSKeyboard.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/MyPOSKeyboard.java index fd19269..b7e8569 100644 --- a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/MyPOSKeyboard.java +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/MyPOSKeyboard.java @@ -1,5 +1,6 @@ package de.javapos.example; +import de.javapos.example.annotation.ThreadSafe; import de.javapos.example.hardware.BaseKeyBoardDriver; import de.javapos.example.queue.JposEventListener; import de.javapos.example.queue.JposEventQueue; diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadFireEvent.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadFireEvent.java index d9e6914..d965d96 100644 --- a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadFireEvent.java +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadFireEvent.java @@ -10,14 +10,14 @@ import jpos.services.EventCallbacks; import de.javapos.example.queue.JposEventQueue; public class ThreadFireEvent extends Thread { + private boolean isDataEventEnabled = true; private final JposEventQueue jposEventQueue; - private boolean isDataEventEnabled; private final EventCallbacks eventCallbacks; + private final ThreadGate freezeEventsGate = new ThreadGate(); public ThreadFireEvent (JposEventQueue jposEventQueue, - boolean isDataEventEnabled, EventCallbacks eventCallbacks) { + boolean isDataEventEnabled, EventCallbacks eventCallbacks) { this.jposEventQueue = jposEventQueue; - this.isDataEventEnabled = isDataEventEnabled; this.eventCallbacks = eventCallbacks; } @@ -26,14 +26,24 @@ public class ThreadFireEvent extends Thread { while (true) { if (this.jposEventQueue.getNumberOfEvents() != 0) { + + try { + this.freezeEventsGate.await(); + } catch (InterruptedException e1) { + //We are implementing the interruption policy in this class + //for this thread. So, this is permitted. + //End of thread. + break; + } + //PROBLEMA: si DataEventEnabled es false y lo unico que hay en la cola son eventos de datos //me meto en un bucle que va a dejar la CPU frita... :/ if (this.isDataEventEnabled) { try { jposEvent = this.jposEventQueue.getEvent(); } catch (InterruptedException e) { - //restore interrupt status. - Thread.currentThread().interrupt(); + //We are implementing the interruption policy in this class + //for this thread. So, this is permitted. //End of thread. break; } @@ -43,7 +53,6 @@ public class ThreadFireEvent extends Thread { } } - //TODO: Bloquearme hasta que freezeEvent sea false if (jposEvent instanceof DataEvent) { this.eventCallbacks.fireDataEvent((DataEvent)jposEvent); @@ -64,4 +73,17 @@ public class ThreadFireEvent extends Thread { } } } + + public void setFreezeEvents (boolean freezeEvents) { + if (freezeEvents) { + this.freezeEventsGate.close(); + } + else { + this.freezeEventsGate.open(); + } + } + + public void setDataEventEnabled (boolean dataEventEnabled) { + this.isDataEventEnabled = dataEventEnabled; + } } diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadGate.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadGate.java new file mode 100644 index 0000000..5fb88ea --- /dev/null +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadGate.java @@ -0,0 +1,34 @@ +package de.javapos.example; + +import de.javapos.example.annotation.GuardedBy; +import de.javapos.example.annotation.ThreadSafe; + +/** + * See: §Java Concurrency in practice 14.2.6 + * @author + * + */ +@ThreadSafe +public class ThreadGate { + //CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n) + @GuardedBy("this") private boolean isOpen; + @GuardedBy("this") private int generation; + + + public synchronized void close() { + isOpen = false; + } + + public synchronized void open() { + ++generation; + isOpen = true; + notifyAll(); + } + + //BLOCKS-UNTIL: opened-since(generation on entry) + public synchronized void await() throws InterruptedException { + int arrivalGeneration = generation; + while (!isOpen && arrivalGeneration == generation) + wait(); + } +} diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadSafe.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadSafe.java deleted file mode 100644 index dc3a60a..0000000 --- a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/ThreadSafe.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.javapos.example; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * Declare classes as thread-safe. It is assumed that objects of these - * classes are thread-safe and can be shared between different threads. * - */ -@Target(value = ElementType.TYPE) -public @interface ThreadSafe { - -} diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/GuardedBy.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/GuardedBy.java new file mode 100644 index 0000000..746bf01 --- /dev/null +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/GuardedBy.java @@ -0,0 +1,38 @@ +package de.javapos.example.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; + +/* + * Copyright (c) 2005 Brian Goetz + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + */ + +/** + * GuardedBy + * + * The field or method to which this annotation is applied can only be accessed + * when holding a particular lock, which may be a built-in (synchronization) + * lock, or may be an explicit java.util.concurrent.Lock. + * + * The argument determines which lock guards the annotated field or method: this : + * The string literal "this" means that this field is guarded by the class in + * which it is defined. class-name.this : For inner classes, it may be necessary + * to disambiguate 'this'; the class-name.this designation allows you to specify + * which 'this' reference is intended itself : For reference fields only; the + * object to which the field refers. field-name : The lock object is referenced + * by the (instance or static) field specified by field-name. + * class-name.field-name : The lock object is reference by the static field + * specified by class-name.field-name. method-name() : The lock object is + * returned by calling the named nil-ary method. class-name.class : The Class + * object for the specified class should be used as the lock object. + */ +@Target( { ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.CLASS) +public @interface GuardedBy { + String value(); +} \ No newline at end of file diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/ThreadSafe.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/ThreadSafe.java new file mode 100644 index 0000000..c96c5b2 --- /dev/null +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/annotation/ThreadSafe.java @@ -0,0 +1,22 @@ +package de.javapos.example.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ThreadSafe + * + * The class to which this annotation is applied is thread-safe. This means that + * no sequences of accesses (reads and writes to public fields, calls to public + * methods) may put the object into an invalid state, regardless of the + * interleaving of those actions by the runtime, and without requiring any + * additional synchronization or coordination on the part of the caller. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface ThreadSafe { +} \ No newline at end of file diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/hardware/KeyBoardDeviceLinux.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/hardware/KeyBoardDeviceLinux.java index 5f9828b..0879733 100644 --- a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/hardware/KeyBoardDeviceLinux.java +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/hardware/KeyBoardDeviceLinux.java @@ -34,6 +34,7 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { private FileChannel fileChannelLock; private DataInputStream device; private boolean isClaimed; + private boolean isEnabled; //Usando volatile porque cumplo las condiciones de uso. Ver Java Concurrency in Practice PAG???? private volatile boolean autoDisable; private JposEventListener eventListener; @@ -61,13 +62,14 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { @Override public void claim(int time) throws JposException { FileLock lock = null; + FileChannel fileChannelLock = null; if (this.isClaimed) { return; } try { - this.fileChannelLock = new FileOutputStream(javaposKeyBoardLock).getChannel(); + fileChannelLock = new FileOutputStream(javaposKeyBoardLock).getChannel(); } catch (FileNotFoundException e) { throw new JposException(JposConst.JPOS_E_NOTCLAIMED, "File not found.",e); } @@ -75,12 +77,24 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { if (time == -1) { try { //This method is not aware interrupt. :( - lock = this.fileChannelLock.lock(); + lock = fileChannelLock.lock(); //I do not like catching RunTimeExceptions but I have no choice... } catch (OverlappingFileLockException e) { logger.warn("Concurrent access in claim method not supported without synchronization or you have" + "more than one instances of the KeyBoardDeviceLinux class", e); + try { + fileChannelLock.close(); + } catch (IOException e1) { + //The first exception is more important. + logger.warn("Error while closing the file lock", e1); + } } catch (IOException e) { + try { + fileChannelLock.close(); + } catch (IOException e1) { + //The first exception is more important. + logger.warn("Error while closing the file lock", e1); + } throw new JposException(JposConst.JPOS_E_CLAIMED, "Error while trying to claim device.",e); } } @@ -89,34 +103,59 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { //Espera activa. ¿Alguna idea de cómo hacer esto sin espera activa? :( while((System.nanoTime() <= lastTime) && (lock == null)) { try { - if ((lock = this.fileChannelLock.tryLock()) != null) { + if ((lock = fileChannelLock.tryLock()) != null) { break; } + this.wait(250); //I do not like catching RunTimeExceptions but I have no choice... } catch (OverlappingFileLockException e) { logger.warn("Concurrent access in claim method not supported without synchronization or you have" + "more than one instances of the KeyBoardDeviceLinux class", e); + try { + fileChannelLock.close(); + } catch (IOException e1) { + //The first exception is more important. + logger.warn("Error while closing the file lock", e1); + } } catch (IOException e) { - throw new JposException(JposConst.JPOS_E_CLAIMED, "Error while trying to claim device.",e); - } - try { - this.wait(250); + try { + fileChannelLock.close(); + } catch (IOException e1) { + //The first exception is more important. + logger.warn("Error while closing the file lock", e1); + } + throw new JposException(JposConst.JPOS_E_CLAIMED, "Error while trying to claim device.",e); } catch(InterruptedException e) { - //restore interrupt status. + try { + fileChannelLock.close(); + } catch (IOException e1) { + //The first exception is more important. + logger.warn("Error while closing the file lock", e1); + } + //restore interrupt status. Thread.currentThread().interrupt(); - } + throw new JposException(JposConst.JPOSERR, "Interrupt exception detected.", e); + } } if (lock == null) { - throw new JposException(JposConst.JPOS_E_TIMEOUT, "Timeout while trying to claim device.", null); + try { + fileChannelLock.close(); + } catch (IOException e) { + //The timeout exception is more important. + logger.warn("Error while closing the file lock", e); + } + throw new JposException(JposConst.JPOS_E_TIMEOUT, "Timeout while trying to claim device."); } } if (lock != null) { - //Just when concurrent access without synchronization (this method is not tread-safe) you could see - //null value. claim does not support concurrent access in itself anyway I take counter measures in case + //Just when concurrent access without synchronization (this method is not tread-safe) or having more + //than one instances of this class you could see null value. + //claim does not support concurrent access in itself. Anyway I take counter measures in case //of someone forgets it. this.lock = lock; + this.fileChannelLock = fileChannelLock; this.isClaimed = true; } } @@ -167,6 +206,9 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { */ @Override public void enable() throws JposException { + if (this.isEnabled) { + return; + } if (this.deviceName == null) { throw new JposException(JposConst.JPOSERR, "There is not an assigned device", new NullPointerException("The deviceName field has null value")); @@ -194,10 +236,7 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { //dispositivo del SO se borro o cualquier otra cosa? Aqui veria el hilo muerto pero en realidad el //dispositivo no fue deshabilitado si no que el hilo murio de forma inesperada Y NO SE CERRO this.device!!!... //Si soluciono este ultimo escollo ¿sí me podre basar en si el hilo está o no muerto? - if (this.thread.isAlive()) { - throw new JposException(JposConst.JPOSERR, "The device was not disabled.", null); - } - else { + if (!this.thread.isAlive()) { try { this.device = new DataInputStream(Channels.newInputStream(new FileInputStream(this.deviceName).getChannel())); } catch (FileNotFoundException e) { @@ -216,29 +255,33 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { this.thread = new Thread (task, "KeyBoardDeviceLinux-Thread"); this.thread.setUncaughtExceptionHandler(new DriverHWUncaughtExceptionHandler()); this.thread.start(); + this.isEnabled = true; } } @Override public void disable() throws JposException { + + if (!this.isEnabled) { + return; + } + //This method releases the Java NIO channel. It is thread safety. :) see: Interruptible this.thread.interrupt(); try { this.thread.join(); } catch (InterruptedException e) { //restore interrupt status. - //QUE PASA SIN LLAMAN A INTERRUPT EN JOIN Y EL HILO NUNCA MURIERA - //TAL Y COMO ESTA HECHO EL HILO NO TARDARA MUCHO EN MORIR (espero) ASI QUE - //AUNQUE ALGO ME INTERRUMPA NO DEBERIA PASAR GRAN COSA, LO UNICO QUE SI OTRO HILO - //JUSTO AQUI INTENTA HACER ENABLE PUEDE QUE VEA EL HILO TODAVIA VIVO AUNQUE YA SE HABIA - //PASADO POR EL DISABLE, ¿¿¿PERO TAN POCO ES UN GRAN PROBLEMA ESO??? LUEGO PARA MÍ QUE ¿ESTO ESTA OK? - Thread.currentThread().interrupt(); + Thread.currentThread().interrupt(); + throw new JposException(JposConst.JPOSERR, "Interrupt exception detected.", e); } + + this.isEnabled = false; } @Override public boolean isEnabled() { - return this.thread.isAlive(); + return this.isEnabled; } @Override @@ -349,7 +392,12 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { value = ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); if (type == KeyBoardDeviceLinux.EV_KEY) { - eventListener.inputAvailable(code); + //Problema aqui con eventListener y que vea != null y luego justo dentro del if + //otro hilo me lo quite... + if (eventListener != null) { + eventListener.inputAvailable(code); + } + logger.debug("Captured key " + "type: " + type + " code: " + code + " value: " + value); if (autoDisable) { break; @@ -363,8 +411,9 @@ public class KeyBoardDeviceLinux implements BaseKeyBoardDriver { //logger.debug or logger.error if it was an error I am not going to log it... :/ logger.debug("Finished KeyBoardDeviceLinux Thread", e); } finally { - //This method releases the Java NIO channels. It is thread safety :) see: Interruptible + //This method releases the Java NIO channels (this.device). It is thread safety :) see: Interruptible this.thread.interrupt(); + this.device = null; //¿Realmente esto es necesario? (no lo creo :/ ) //SI EL HILO MUERE ¿ESTOY DEJANDO EL ESTADO DEL DISPOSITIVO LOGICO JAVAPOS //EN UN MODO CONSISTENTE? //liberar el LOCK SI EL HILO MUERE DE FORMA INESPERADA???? NO, JavaPOS no dice nada. diff --git a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/queue/JposEventQueueImpl.java b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/queue/JposEventQueueImpl.java index 6e90064..0f71378 100644 --- a/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/queue/JposEventQueueImpl.java +++ b/JavaPOS/KeyBoardDriver/src/main/java/de/javapos/example/queue/JposEventQueueImpl.java @@ -1,7 +1,11 @@ package de.javapos.example.queue; +import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; + +import jpos.events.DataEvent; +import jpos.events.ErrorEvent; import jpos.events.JposEvent; public class JposEventQueueImpl implements JposEventQueue { @@ -37,7 +41,13 @@ public class JposEventQueueImpl implements JposEventQueue { @Override public void checkEvents() { - // TODO Auto-generated method stub + //WIP sorry... :/ + Iterator iterator = this.linkedBlockingQueue.iterator(); + while (iterator.hasNext()) { + if (!(iterator.next() instanceof DataEvent) || !(iterator.next() instanceof ErrorEvent)) { + + } + } } @Override @@ -52,7 +62,7 @@ public class JposEventQueueImpl implements JposEventQueue { @Override public JposEvent peekElement(int paramInt) { - return null; + return this.linkedBlockingQueue.peek(); } @Override -- 2.1.4