| 
					
				 | 
			
			
				@@ -0,0 +1,245 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/* 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Copyright (C) 2011 Google Inc. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * use this file except in compliance with the License. You may obtain a copy of 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * the License at 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * http://www.apache.org/licenses/LICENSE-2.0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Unless required by applicable law or agreed to in writing, software 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * License for the specific language governing permissions and limitations under 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * the License. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+package org.ros.android.hokuyo; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.ArrayList; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.List; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.CountDownLatch; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.TimeUnit; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import junit.framework.TestCase; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.RosCore; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.internal.node.DefaultNode; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.message.MessageListener; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.message.sensor_msgs.LaserScan; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.DefaultNodeRunner; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.Node; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.NodeConfiguration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.NodeMain; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.NodeRunner; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.ros.node.topic.Subscriber; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.google.common.annotations.VisibleForTesting; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @author moesenle@google.com (Lorenz Moesenlechner) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+public class LaserScanPublisherIntegrationTest extends TestCase { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private class FakeLaserDevice implements LaserScannerDevice { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static final int SCAN_PUBLISH_FREQUENCY = 10; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private class RepeatingScanGeneratorThread extends Thread { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      private LaserScanListener listener; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      private int frequency; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      public RepeatingScanGeneratorThread(int frequency, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          LaserScanListener listener) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.listener = listener; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.frequency = frequency; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      public void run() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          while (!Thread.currentThread().isInterrupted()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            listener.onNewLaserScan(makeFakeScan()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Thread.sleep((long) (1000f / frequency)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } catch (InterruptedException e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // Cancelable 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      public void cancel() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        interrupt(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private RepeatingScanGeneratorThread scanGeneratorThread; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private int numberOfRangeValues; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public FakeLaserDevice() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      numberOfRangeValues = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public FakeLaserDevice(int numberOfRangeValues) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.numberOfRangeValues = numberOfRangeValues; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void startScanning(LaserScanListener listener) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (scanGeneratorThread != null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        scanGeneratorThread.cancel(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      scanGeneratorThread = new RepeatingScanGeneratorThread( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          SCAN_PUBLISH_FREQUENCY, listener); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      scanGeneratorThread.start(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void shutdown() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if (scanGeneratorThread != null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        scanGeneratorThread.cancel(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public LaserScannerConfiguration getConfiguration() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return new FakeLaserScannerConfiguration(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @VisibleForTesting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    org.ros.android.hokuyo.LaserScan makeFakeScan() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      List<Integer> fakeRangeMeasurements = new ArrayList<Integer>( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          numberOfRangeValues); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      for (int i = 0; i < numberOfRangeValues; i++) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        fakeRangeMeasurements.add(0); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return new org.ros.android.hokuyo.LaserScan(0.0, fakeRangeMeasurements); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private final class FakeLaserScannerConfiguration implements 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      LaserScannerConfiguration { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public String getModel() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return "TestLaserScanner"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getMinimumMeasurment() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getMaximumMeasurement() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getTotalSteps() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 3; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getFirstStep() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getLastStep() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getFrontStep() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public int getStandardMotorSpeed() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public float getAngleIncrement() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return (float) Math.PI; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public float getMinimumAngle() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return (float) -Math.PI; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public float getMaximumAngle() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return (float) Math.PI; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public float getTimeIncrement() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public float getScanTime() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      return 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  public void testLaserScanPublisher() throws InterruptedException { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    RosCore core = RosCore.newPrivate(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    core.start(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    assertTrue(core.awaitStart(1, TimeUnit.SECONDS)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    final NodeConfiguration nodeConfiguration = NodeConfiguration 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        .newPrivate(core.getUri()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    final NodeRunner runner = DefaultNodeRunner.newDefault(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    final CountDownLatch laserScanReceived = new CountDownLatch(1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    runner.run(new NodeMain() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      private FakeLaserDevice fakeLaser; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      private Node node; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      public void main(Node node) throws Exception { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.node = node; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Subscriber<org.ros.message.sensor_msgs.LaserScan> laserScanSubscriber = node 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            .newSubscriber("laser", "sensor_msgs/LaserScan", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                new MessageListener<org.ros.message.sensor_msgs.LaserScan>() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  public void onNewMessage(LaserScan message) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    assertTrue(message.ranges.length == 3); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    laserScanReceived.countDown(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // Check that fake laser data is equal to the received 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // message 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        assertTrue(laserScanSubscriber.awaitRegistration(1, TimeUnit.SECONDS)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        fakeLaser = new FakeLaserDevice(3); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        runner.run(new LaserScanPublisher(fakeLaser), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            nodeConfiguration.setNodeName("laser_scan_publisher_test")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      public void shutdown() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        fakeLaser.shutdown(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        node.shutdown(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }, nodeConfiguration.setNodeName("android_hokuyo_test_node")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    assertTrue(laserScanReceived.await(1, TimeUnit.SECONDS)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    runner.shutdown(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  public void testLaserScannerInvalidNumberOfMeasurements() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    FakeLaserDevice fakeLaser = new FakeLaserDevice(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    LaserScanPublisher scanPublisher = new LaserScanPublisher(fakeLaser); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    NodeConfiguration nodeConfiguration = NodeConfiguration.newPrivate(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    scanPublisher.setNode(new DefaultNode(nodeConfiguration.setNodeName("android_hokuyo_test_node"))); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    try { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      scanPublisher.toLaserScanMessage("/base_scan", fakeLaser.makeFakeScan()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      fail(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } catch (IllegalStateException e) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // This should throw because our laser scan has too few range 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // measurements. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // It expects three according to our configuration. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |