Coverage Report - ca.uhn.hl7v2.app.ConnectionHub
 
Classes in this File Line Coverage Branch Coverage Complexity
ConnectionHub
58%
54/93
45%
9/20
1.429
ConnectionHub$1
100%
5/5
N/A
1.429
ConnectionHub$CountingMap
96%
24/25
91%
11/12
1.429
ConnectionHub$CountingMap$Count
100%
11/11
75%
3/4
1.429
 
 1  
 /**
 2  
 The contents of this file are subject to the Mozilla Public License Version 1.1 
 3  
 (the "License"); you may not use this file except in compliance with the License. 
 4  
 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
 5  
 Software distributed under the License is distributed on an "AS IS" basis, 
 6  
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
 7  
 specific language governing rights and limitations under the License. 
 8  
 
 9  
 The Original Code is "ConnectionHub.java".  Description: 
 10  
 "Provides access to shared HL7 Connections" 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2001.  All Rights Reserved. 
 14  
 
 15  
 Contributor(s): ______________________________________. 
 16  
 
 17  
 Alternatively, the contents of this file may be used under the terms of the 
 18  
 GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
 19  
 applicable instead of those above.  If you wish to allow use of your version of this 
 20  
 file only under the terms of the GPL and not to allow others to use your version 
 21  
 of this file under the MPL, indicate your decision by deleting  the provisions above 
 22  
 and replace  them with the notice and other provisions required by the GPL License.  
 23  
 If you do not delete the provisions above, a recipient may use your version of 
 24  
 this file under either the MPL or the GPL. 
 25  
  */
 26  
 
 27  
 package ca.uhn.hl7v2.app;
 28  
 
 29  
 import java.util.Collections;
 30  
 import java.util.Map;
 31  
 import java.util.Set;
 32  
 import java.util.concurrent.ConcurrentHashMap;
 33  
 import java.util.concurrent.ConcurrentMap;
 34  
 import java.util.concurrent.ExecutorService;
 35  
 
 36  
 import ca.uhn.hl7v2.DefaultHapiContext;
 37  
 import ca.uhn.hl7v2.HL7Exception;
 38  
 import ca.uhn.hl7v2.HapiContext;
 39  
 import ca.uhn.hl7v2.HapiContextSupport;
 40  
 import ca.uhn.hl7v2.concurrent.DefaultExecutorService;
 41  
 import ca.uhn.hl7v2.llp.LowerLayerProtocol;
 42  
 import ca.uhn.hl7v2.parser.Parser;
 43  
 import ca.uhn.hl7v2.util.ReflectionUtil;
 44  
 import ca.uhn.hl7v2.util.SocketFactory;
 45  
 import org.slf4j.Logger;
 46  
 import org.slf4j.LoggerFactory;
 47  
 
 48  
 /**
 49  
  * <p>
 50  
  * Provides access to shared HL7 Connections. The ConnectionHub has at most one connection to any
 51  
  * given address at any time.
 52  
  * </p>
 53  
  * <p>
 54  
  * <b>Synchronization Note:</b> This class should be safe to use in a multithreaded environment. A
 55  
  * synchronization mutex is maintained for any given target host and port, so that if two threads
 56  
  * are trying to connect to two separate destinations neither will block, but if two threads are
 57  
  * trying to connect to the same destination, one will block until the other has finished trying.
 58  
  * Use caution if this class is to be used in an environment where a very large (over 1000) number
 59  
  * of target host/port destinations will be accessed at the same time.
 60  
  * </p>
 61  
  * 
 62  
  * @author Bryan Tripp
 63  
  */
 64  
 public class ConnectionHub extends HapiContextSupport {
 65  
 
 66  5
         private static volatile ConnectionHub instance = null;
 67  5
         private static final Logger log = LoggerFactory.getLogger(ConnectionHub.class);
 68  
         /**
 69  
          * Set a system property with this key to a string containing an integer larger than the default
 70  
          * ("1000") if you need to connect to a very large number of targets at the same time in a
 71  
          * multithreaded environment.
 72  
          */
 73  5
         public static final String MAX_CONCURRENT_TARGETS = ConnectionHub.class.getName() + ".maxSize";
 74  75
         private final ConcurrentMap<String, String> connectionMutexes = new ConcurrentHashMap<String, String>();
 75  
         private final CountingMap<ConnectionData, Connection> connections;
 76  
 
 77  
         /** Creates a new instance of ConnectionHub */
 78  
         private ConnectionHub(ExecutorService executorService) {
 79  0
                 this(new DefaultHapiContext(executorService));
 80  0
         }
 81  
 
 82  
         private ConnectionHub(HapiContext context) {
 83  75
                 super(context);
 84  75
                 connections = new CountingMap<ConnectionData, Connection>() {
 85  
 
 86  
                         @Override
 87  
                         protected void dispose(Connection connection) {
 88  115
                                 connection.close();
 89  115
                         }
 90  
 
 91  
                         @Override
 92  
                         protected Connection open(ConnectionData connectionData) throws Exception {
 93  490
                                 return ConnectionFactory
 94  315
                                                 .open(connectionData, getHapiContext().getExecutorService());
 95  
                         }
 96  
 
 97  
                 };
 98  75
         }
 99  
 
 100  
         public Set<? extends ConnectionData> allConnections() {
 101  200
                 return connections.keySet();
 102  
         }
 103  
 
 104  
         /**
 105  
          * @since 2.0
 106  
          */
 107  
         public Connection attach(ConnectionData data) throws HL7Exception {
 108  
                 try {
 109  1340
                         Connection conn = null;
 110  
                         // Disallow establishing same connection targets concurrently
 111  1340
                         connectionMutexes.putIfAbsent(data.toString(), data.toString());
 112  1340
                         String mutex = connectionMutexes.get(data.toString());
 113  1340
                         synchronized (mutex) {
 114  1340
                                 discardConnectionIfStale(connections.get(data));
 115  
                                 // Create connection or increase counter
 116  1340
                                 conn = connections.put(data);
 117  1200
                         }
 118  1200
                         return conn;
 119  140
                 } catch (Exception e) {
 120  140
                         log.debug("Failed to attach", e);
 121  140
                         throw new HL7Exception("Cannot open connection to " + data.getHost() + ":"
 122  140
                                         + data.getPort() + "/" + data.getPort2(), e);
 123  
                 }
 124  
         }
 125  
 
 126  
     /**
 127  
      * Returns a Connection to the given address, opening this Connection if necessary. The given
 128  
      * Parser will only be used if a new Connection is opened, so there is no guarantee that the
 129  
      * Connection returned will be using the Parser you provide. If you need explicit access to the
 130  
      * Parser the Connection is using, call <code>Connection.getParser()</code>.
 131  
      *
 132  
      * @since 2.1
 133  
      */
 134  
     public Connection attach(String host, int port, boolean tls) throws HL7Exception {
 135  1400
         return attach(new ConnectionData(host, port, 0, getHapiContext().getGenericParser(),
 136  700
                 getHapiContext().getLowerLayerProtocol(), tls, getHapiContext()
 137  700
                 .getSocketFactory(), false));
 138  
     }
 139  
 
 140  
         /**
 141  
          * Returns a Connection to the given address, opening this Connection if necessary. The given
 142  
          * Parser will only be used if a new Connection is opened, so there is no guarantee that the
 143  
          * Connection returned will be using the Parser you provide. If you need explicit access to the
 144  
          * Parser the Connection is using, call <code>Connection.getParser()</code>.
 145  
          * 
 146  
          * @since 2.2
 147  
          */
 148  
         public Connection attachLazily(String host, int port, boolean tls) throws HL7Exception {
 149  10
                 return attach(new ConnectionData(host, port, 0, getHapiContext().getGenericParser(),
 150  5
                                 getHapiContext().getLowerLayerProtocol(), tls, getHapiContext()
 151  5
                                                 .getSocketFactory(), true));
 152  
         }
 153  
 
 154  
     /**
 155  
      * @since 2.0
 156  
      */
 157  
     public Connection attach(String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception {
 158  1110
         return attach(new ConnectionData(host, outboundPort, inboundPort, getHapiContext()
 159  555
                 .getGenericParser(), getHapiContext().getLowerLayerProtocol(), tls,
 160  555
                 getHapiContext().getSocketFactory(), false));
 161  
     }
 162  
 
 163  
         /**
 164  
          * @since 2.2
 165  
          */
 166  
         public Connection attachLazily(String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception {
 167  0
                 return attach(new ConnectionData(host, outboundPort, inboundPort, getHapiContext()
 168  0
                                 .getGenericParser(), getHapiContext().getLowerLayerProtocol(), tls,
 169  0
                                 getHapiContext().getSocketFactory(), true));
 170  
         }
 171  
 
 172  
         /**
 173  
          * @since 2.0
 174  
          */
 175  
         public Connection attach(String host, int outboundPort, int inboundPort, Parser parser,
 176  
                         Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception {
 177  0
                 return attach(host, outboundPort, inboundPort, parser, llpClass, false);
 178  
         }
 179  
 
 180  
     /**
 181  
      * @since 2.0
 182  
      */
 183  
     public Connection attachLazily(String host, int outboundPort, int inboundPort, Parser parser,
 184  
                              Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception {
 185  0
         return attachLazily(host, outboundPort, inboundPort, parser, llpClass, false);
 186  
     }
 187  
 
 188  
         /**
 189  
          * @since 2.0
 190  
          */
 191  
         public Connection attach(String host, int outboundPort, int inboundPort, Parser parser,
 192  
                 Class<? extends LowerLayerProtocol> llpClass, boolean tls) throws HL7Exception {
 193  0
                 LowerLayerProtocol llp = ReflectionUtil.instantiate(llpClass);
 194  0
                 return attach(host, outboundPort, inboundPort, parser, llp, tls);
 195  
         }
 196  
 
 197  
     public Connection attachLazily(String host, int outboundPort, int inboundPort, Parser parser,
 198  
                              Class<? extends LowerLayerProtocol> llpClass, boolean tls) throws HL7Exception {
 199  0
         LowerLayerProtocol llp = ReflectionUtil.instantiate(llpClass);
 200  0
         return attachLazily(host, outboundPort, inboundPort, parser, llp, tls);
 201  
     }
 202  
 
 203  
         /**
 204  
          * @since 2.0
 205  
          */
 206  
         public Connection attach(String host, int outboundPort, int inboundPort, Parser parser,
 207  
                         LowerLayerProtocol llp, boolean tls) throws HL7Exception {
 208  10
                 return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, null, false));
 209  
         }
 210  
 
 211  
     /**
 212  
      * @since 2.2
 213  
      */
 214  
     public Connection attachLazily(String host, int outboundPort, int inboundPort, Parser parser,
 215  
                              LowerLayerProtocol llp, boolean tls) throws HL7Exception {
 216  0
         return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, null, true));
 217  
     }
 218  
 
 219  
         /**
 220  
          * @since 2.1
 221  
          */
 222  
         public Connection attach(String host, int outboundPort, int inboundPort, Parser parser, LowerLayerProtocol llp,
 223  
                              boolean tls, SocketFactory socketFactory) throws HL7Exception {
 224  0
                 return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, socketFactory, false));
 225  
         }
 226  
 
 227  
     /**
 228  
      * @since 2.1
 229  
      */
 230  
     public Connection attachLazily(String host, int outboundPort, int inboundPort, Parser parser, LowerLayerProtocol llp,
 231  
                              boolean tls, SocketFactory socketFactory) throws HL7Exception {
 232  0
         return attach(new ConnectionData(host, outboundPort, inboundPort, parser, llp, tls, socketFactory, true));
 233  
     }
 234  
 
 235  
         /**
 236  
          * @since 2.1
 237  
          */
 238  
         public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp,
 239  
                              boolean tls, SocketFactory socketFactory) throws HL7Exception {
 240  0
                 return attach(new ConnectionData(host, port, 0, parser, llp, tls, socketFactory, false));
 241  
         }
 242  
 
 243  
     /**
 244  
      * @since 2.1
 245  
      */
 246  
     public Connection attachLazily(String host, int port, Parser parser, LowerLayerProtocol llp,
 247  
                              boolean tls, SocketFactory socketFactory) throws HL7Exception {
 248  0
         return attach(new ConnectionData(host, port, 0, parser, llp, tls, socketFactory, true));
 249  
     }
 250  
 
 251  
         /**
 252  
          * @since 2.1
 253  
          */
 254  
         public Connection attach(DefaultHapiContext hapiContext, String host, int port, boolean tls) throws HL7Exception {
 255  110
                 return attach(new ConnectionData(host, port, 0, hapiContext.getGenericParser(), hapiContext.getLowerLayerProtocol(),
 256  55
                 tls, hapiContext.getSocketFactory(), false));
 257  
         }
 258  
 
 259  
     /**
 260  
      * @since 2.2
 261  
      */
 262  
     public Connection attachLazily(DefaultHapiContext hapiContext, String host, int port, boolean tls) throws HL7Exception {
 263  30
         return attach(new ConnectionData(host, port, 0, hapiContext.getGenericParser(), hapiContext.getLowerLayerProtocol(),
 264  15
                 tls, hapiContext.getSocketFactory(), true));
 265  
     }
 266  
 
 267  
         /**
 268  
          * @since 2.1
 269  
          */
 270  
         public Connection attach(DefaultHapiContext hapiContext, String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception {
 271  0
                 return attach(new ConnectionData(host, outboundPort, inboundPort, hapiContext.getGenericParser(),
 272  0
                 hapiContext.getLowerLayerProtocol(), tls, hapiContext.getSocketFactory(), false));
 273  
         }
 274  
 
 275  
     /**
 276  
      * @since 2.2
 277  
      */
 278  
     public Connection attachLazily(DefaultHapiContext hapiContext, String host, int outboundPort, int inboundPort, boolean tls) throws HL7Exception {
 279  0
         return attach(new ConnectionData(host, outboundPort, inboundPort, hapiContext.getGenericParser(),
 280  0
                 hapiContext.getLowerLayerProtocol(), tls, hapiContext.getSocketFactory(), true));
 281  
     }
 282  
 
 283  
         /**
 284  
          * @since 1.2
 285  
          */
 286  
         public Connection attach(String host, int port, Parser parser,
 287  
                         Class<? extends LowerLayerProtocol> llpClass) throws HL7Exception {
 288  0
                 return attach(host, port, parser, llpClass, false);
 289  
         }
 290  
 
 291  
         /**
 292  
          * @since 2.0
 293  
          */
 294  
         public Connection attach(String host, int port, Parser parser,
 295  
                         Class<? extends LowerLayerProtocol> llpClass, boolean tls) throws HL7Exception {
 296  0
                 return attach(host, port, 0, parser, llpClass, tls);
 297  
         }
 298  
 
 299  
         /**
 300  
          * @since 2.0
 301  
          */
 302  
         public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp)
 303  
                         throws HL7Exception {
 304  0
                 return attach(host, port, 0, parser, llp, false);
 305  
         }
 306  
 
 307  
     /**
 308  
      * @since 2.2
 309  
      */
 310  
     public Connection attachLazily(String host, int port, Parser parser, LowerLayerProtocol llp)
 311  
             throws HL7Exception {
 312  0
         return attachLazily(host, port, 0, parser, llp, false);
 313  
     }
 314  
 
 315  
 
 316  
         /**
 317  
          * @since 2.0
 318  
          */
 319  
         public Connection attach(String host, int port, Parser parser, LowerLayerProtocol llp,
 320  
                         boolean tls) throws HL7Exception {
 321  10
                 return attach(host, port, 0, parser, llp, tls);
 322  
         }
 323  
 
 324  
     /**
 325  
      * @since 2.0
 326  
      */
 327  
     public Connection attachLazily(String host, int port, Parser parser, LowerLayerProtocol llp,
 328  
                              boolean tls) throws HL7Exception {
 329  0
         return attachLazily(host, port, 0, parser, llp, tls);
 330  
     }
 331  
 
 332  
         /**
 333  
          * Informs the ConnectionHub that you are done with the given Connection - if no other code is
 334  
          * using it, it will be closed, so you should not attempt to use a Connection after detaching
 335  
          * from it. If the connection is not enlisted, this method does nothing.
 336  
          */
 337  
         public void detach(Connection c) {
 338  55
                 ConnectionData cd = connections.find(c);
 339  55
                 if (cd != null)
 340  45
                         connections.remove(cd);
 341  55
         }
 342  
 
 343  
         /**
 344  
          * Closes and discards the given Connection so that it can not be returned in subsequent calls
 345  
          * to attach(). This method is to be used when there is a problem with a Connection, e.g. socket
 346  
          * connection closed by remote host.
 347  
          */
 348  
         public void discard(Connection c) {
 349  40
                 ConnectionData cd = connections.find(c);
 350  40
                 if (cd != null)
 351  40
                         connections.removeAllOf(cd);
 352  40
         }
 353  
 
 354  
     /**
 355  
      * Closes and discards all connections.
 356  
      */
 357  
         public void discardAll() {
 358  115
                 for (ConnectionData cd : allConnections()) {
 359  40
                         connections.removeAllOf(cd);
 360  40
                 }
 361  115
         }
 362  
 
 363  
         private void discardConnectionIfStale(Connection conn) {
 364  1340
                 if (conn != null && !conn.isOpen()) {
 365  40
                         log.info("Discarding connection which appears to be closed. Remote addr: {}",
 366  20
                                         conn.getRemoteAddress());
 367  20
                         discard(conn);
 368  20
                         conn = null;
 369  
                 }
 370  1340
         }
 371  
 
 372  
         public Connection getKnownConnection(ConnectionData key) {
 373  0
                 return connections.get(key);
 374  
         }
 375  
 
 376  
         public boolean isOpen(ConnectionData key) {
 377  0
                 return getKnownConnection(key).isOpen();
 378  
         }
 379  
 
 380  
         /**
 381  
          * Returns the singleton instance of ConnectionHub
 382  
          * 
 383  
          * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub.
 384  
          *             See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub.
 385  
          */
 386  
         public static ConnectionHub getInstance() {
 387  0
                 return getInstance(DefaultExecutorService.getDefaultService());
 388  
         }
 389  
 
 390  
         /**
 391  
          * Returns the singleton instance of ConnectionHub.
 392  
          * 
 393  
          * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub.
 394  
          *             See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub.
 395  
          */
 396  
         public synchronized static ConnectionHub getInstance(ExecutorService service) {
 397  0
                 if (instance == null || service.isShutdown()) {
 398  0
                         instance = new ConnectionHub(service);
 399  
                 }
 400  0
                 return instance;
 401  
         }
 402  
 
 403  
         /**
 404  
          * Returns the singleton instance of ConnectionHub.
 405  
          * 
 406  
          * @deprecated Use {@link HapiContext#getConnectionHub()} to get an instance of ConnectionHub.
 407  
          *             See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a> for an example of how to use ConnectionHub.
 408  
          */
 409  
         public static ConnectionHub getInstance(HapiContext context) {
 410  0
                 if (instance == null || context.getExecutorService().isShutdown()) {
 411  0
                         instance = new ConnectionHub(context);
 412  
                 }
 413  0
                 return instance;
 414  
         }
 415  
 
 416  
         /**
 417  
          * <p>
 418  
          * Returns a new (non-singleton) instance of the ConnectionHub which uses the given executor
 419  
          * service.
 420  
          * </p>
 421  
          * <p>
 422  
          * See <a href="http://hl7api.sourceforge.net/xref/ca/uhn/hl7v2/examples/SendAndReceiveAMessage.html">this example page</a>
 423  
          * for an example of how to use ConnectionHub.
 424  
          * </p>
 425  
          */
 426  
         public synchronized static ConnectionHub getNewInstance(HapiContext context) {
 427  75
                 return new ConnectionHub(context);
 428  
         }
 429  
 
 430  
         /**
 431  
          * @deprecated default executor service is shut down automatically
 432  
          */
 433  
         public static void shutdown() {
 434  0
                 ConnectionHub hub = getInstance();
 435  0
                 if (DefaultExecutorService.isDefaultService(hub.getHapiContext().getExecutorService())) {
 436  0
                         hub.getHapiContext().getExecutorService().shutdown();
 437  0
                         instance = null;
 438  
                 }
 439  0
         }
 440  
 
 441  
         /**
 442  
          * Helper class that implements a map that increases/decreases a counter when an entry is
 443  
          * added/removed. It is furthermore intended that an entry's value is derived from its key.
 444  
          * 
 445  
          * @param <K> key class
 446  
          * @param <D> managed value class
 447  
          */
 448  
         private abstract class CountingMap<K, D> {
 449  
                 private Map<K, Count> content;
 450  
 
 451  75
                 public CountingMap() {
 452  75
                         super();
 453  75
                         content = new ConcurrentHashMap<K, Count>();
 454  75
                 }
 455  
 
 456  
                 protected abstract void dispose(D value);
 457  
 
 458  
                 public K find(D value) {
 459  95
                         for (Map.Entry<K, Count> entry : content.entrySet()) {
 460  92
                                 if (entry.getValue().getValue().equals(value)) {
 461  85
                                         return entry.getKey();
 462  
                                 }
 463  7
                         }
 464  10
                         return null;
 465  
                 }
 466  
 
 467  
                 public D get(K key) {
 468  1340
                         return content.containsKey(key) ? content.get(key).getValue() : null;
 469  
                 }
 470  
 
 471  
                 public Set<K> keySet() {
 472  200
                         return Collections.unmodifiableSet(content.keySet());
 473  
                 }
 474  
 
 475  
                 protected abstract D open(K key) throws Exception;
 476  
 
 477  
                 /**
 478  
                  * If the key exists, the counter is increased. Otherwise, a value is created, and the
 479  
                  * key/value pair is added to the map.
 480  
                  */
 481  
                 public D put(K key) throws Exception {
 482  1340
                         if (content.containsKey(key)) {
 483  1025
                                 return content.put(key, content.get(key).increase()).getValue();
 484  
                         } else {
 485  315
                                 Count c = new Count(open(key));
 486  175
                                 content.put(key, c);
 487  175
                                 return c.getValue();
 488  
                         }
 489  
                 }
 490  
 
 491  
                 /**
 492  
                  * If the counter of the key/value is greater than one, the counter is decreased. Otherwise,
 493  
                  * the entry is removed and the value is cleaned up.
 494  
                  */
 495  
                 public D remove(K key) {
 496  45
                         Count pair = content.get(key);
 497  45
                         if (pair == null)
 498  0
                                 return null;
 499  45
                         if (pair.isLast()) {
 500  35
                                 return removeAllOf(key);
 501  
                         }
 502  10
                         return content.put(key, content.get(key).decrease()).getValue();
 503  
                 }
 504  
 
 505  
                 /**
 506  
                  * The key/value entry is removed and the value is cleaned up.
 507  
                  */
 508  
                 public D removeAllOf(K key) {
 509  115
                         D removed = content.remove(key).value;
 510  115
                         dispose(removed);
 511  115
                         return removed;
 512  
                 }
 513  
 
 514  115
                 private class Count {
 515  
                         private int count;
 516  
                         private D value;
 517  
 
 518  
                         public Count(D value) {
 519  175
                                 this(value, 1);
 520  175
                         }
 521  
 
 522  1210
                         private Count(D value, int number) {
 523  1210
                                 this.value = value;
 524  1210
                                 this.count = number;
 525  1210
                         }
 526  
 
 527  
                         Count decrease() {
 528  10
                                 return !isLast() ? new Count(value, count - 1) : null;
 529  
                         }
 530  
 
 531  
                         public D getValue() {
 532  2347
                                 return value;
 533  
                         }
 534  
 
 535  
                         Count increase() {
 536  1025
                                 return new Count(value, count + 1);
 537  
                         }
 538  
 
 539  
                         boolean isLast() {
 540  55
                                 return count == 1;
 541  
                         }
 542  
 
 543  
                 }
 544  
 
 545  
         }
 546  
 
 547  
 }