[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Fmsystem-commits] [6698] Added sobim object, along with unit tests
From: |
Petur Bjorn Thorsteinsson |
Subject: |
[Fmsystem-commits] [6698] Added sobim object, along with unit tests |
Date: |
Thu, 23 Dec 2010 15:45:51 +0000 |
Revision: 6698
http://svn.sv.gnu.org/viewvc/?view=rev&root=fmsystem&revision=6698
Author: peturbjorn
Date: 2010-12-23 15:45:51 +0000 (Thu, 23 Dec 2010)
Log Message:
-----------
Added sobim object, along with unit tests
Added Paths:
-----------
branches/dev-bim2/property/inc/class.sobim.inc.php
branches/dev-bim2/property/tests/
branches/dev-bim2/property/tests/BIM/
branches/dev-bim2/property/tests/BIM/PropertyBimTestSuite.php
branches/dev-bim2/property/tests/BIM/TestSObim.php
branches/dev-bim2/property/tests/BIM/testData.xml
Added: branches/dev-bim2/property/inc/class.sobim.inc.php
===================================================================
--- branches/dev-bim2/property/inc/class.sobim.inc.php
(rev 0)
+++ branches/dev-bim2/property/inc/class.sobim.inc.php 2010-12-23 15:45:51 UTC
(rev 6698)
@@ -0,0 +1,222 @@
+<?php
+
+
+//phpgw::import_class('property.boitem');
+
+ interface sobim {
+ public $bimTypeTable = 'fm_bim_type';
+ public $bimItemTable = 'fm_bim_data';
+ /*
+ * @return array of BIM objects
+ */
+ public function getAll();
+ /*
+ * @param int id
+ * @return BIMItem
+ */
+ public function getBimObject($bimObjectId);
+ /*
+ * @param string type
+ */
+ public function getBimObjectType($type);
+ /*
+ * @param $description max 512char string, may be null
+ * @param $name non empty string
+ * @throws exception if object type already exists
+ * @return int id of new object type
+ */
+ public function addBimObjectType($name, $description);
+
+ }
+ class sobim_impl implements sobim
+ {
+ /* @var phpgwapi_db_ */
+ private $db;
+
+ public function __construct(& $db) {
+ // $this->db = & $GLOBALS['phpgw']->db;
+ $this->db = $db;
+ }
+ /*
+ * @return Array an array of BimItem objects
+ */
+ public function getAll() {
+ $sql = 'SELECT fm_bim_data.id, fm_bim_type."name" AS "type",
fm_bim_data.guid, fm_bim_data.xml_representation '.
+ 'FROM public.fm_bim_data,
public.fm_bim_type '.
+ 'WHERE fm_bim_data."type" =
fm_bim_type.id;';
+ $bimItemArray = array();
+ $this->db->query($sql);
+ while($this->db->next_record())
+ {
+ $bimItem = new
BimItem($this->db->f('id'),$this->db->f('guid'), $this->db->f('type'),
$this->db->f('xml_representation'));
+ array_push($bimItemArray, $bimItem);
+ }
+
+ return $bimItemArray;
+ }
+ /*
+ * @return boolean
+ */
+ public function addBimObjectType($name, $description = null) {
+ if(is_null($description)) {
+ $sql = "INSERT INTO $this->bimTypeTable (name) VALUES
('$name')";
+ } else {
+ $sql = "INSERT INTO $this->bimTypeTable (name,
description) VALUES ('$name', '$description')";
+ }
+ if(is_null($this->db->query($sql,__LINE__,__FILE__) )){
+ throw new Exception("Error adding object
type!");
+ } else {
+ return true;
+ }
+ }
+
+ public function getBimObject($bimObjectGuid){
+
+ }
+
+ public function getBimObjectType($type) {
+
+ }
+
+
+
+ /**
+ * Retreive any number of items.
+ * @param array $data
+ * @return array Array of zero or more items
+ */
+ public function read(array $data)
+ {
+
+ $select_cols = array(
+ 'i.id',
+ 'i.group_id',
+ 'i.location_id',
+ 'i.vendor_id',
+ 'i.installed'
+ );
+ $from_tables = array('fm_item i');
+ $joins = array(
+ //$this->db->left_join.' fm_item_group g ON i.group_id = g.id',
+ $this->db->left_join.' fm_vendor v ON i.vendor_id = v.id'
+ );
+ $where_clauses = array(' WHERE 1=1');
+
+ if($specific_item_id) {
+ // FIXME Sanitize input!!
+ $where_clauses[] = "i.id = $specific_item_id";
+ }
+
+ $sql = 'SELECT ' . implode($select_cols, ', ') .
+ ' FROM ' . implode($from_tables, ', ') .
+ implode($joins, ' ') .
+ implode($where_clauses, ' AND ');
+
+ $this->db->query($sql);
+ $i = 0;
+ while($this->db->next_record())
+ {
+ $items[$i]['id'] = $this->db->f('id');
+ $items[$i]['group'] = $this->db->f('group_id');
+ $items[$i]['location'] = $this->db->f('location_id');
+ $items[$i]['vendor'] = $this->db->f('vendor_id');
+ $items[$i]['installed']= $this->db->f('installed');
+
+ $i++;
+ }
+
+ return $items;
+ }
+
+
+ /**
+ * Creates fully populated objects out of an item array.
+ *
+ * @param array $items Array of items in the same format as that
returned from get_items().
+ * @return mixed Array of item objects og null if failed.
+ */
+ private function populate(array $items)
+ {
+ if(!is_array($items))
+ {
+ return null;
+ }
+
+ $return_objects = array();
+ $sogroup = property_sogroup::singleton();
+
+ foreach($items as $item)
+ {
+ $item_obj = new property_boitem($items['installed_date']);
+ $item_obj->set_group($sogroup->get($item['group_id']));
+
+ $return_objects[] = $item_obj;
+ }
+
+ return $return_objects;
+ }
+
+
+ /**
+ * Save changes on an item to database or insert a new one if ID is
empty.
+ *
+ * @param property_boitem $obj
+ */
+ public function save(property_boitem $obj)
+ {
+ // If item has an ID, do an update, otherwise, do an insert
+ $ins_or_upd = ($obj->get_id() != null ? 'UPDATE' : 'INSERT INTO');
+ $table = 'fm_item';
+ $cols = array('id', 'group_id', 'location_id', 'vendor_id',
'installed');
+ $values = array($obj->get_id(),
+ $obj->get_group()->get_id(),
+ $obj->get_location_id(),
+ $obj->get_vendor_id(),
+ $obj->get_installed_date());
+ }
+
+
+ /**
+ * Get total number of records (rows) in item table
+ *
+ * @return integer No. of records
+ */
+ public function total_records()
+ {
+ $sql = 'SELECT COUNT(id) AS rows FROM fm_item';
+
+ $this->db->query($sql);
+ // Move pointer to first row
+ $this->db->next_record();
+ // Get value of 'rows' column
+ return (int) $this->db->f('rows');
+ }
+ }
+
+ class BimItem {
+ private $databaseId;
+ private $guid;
+ private $type;
+ private $xml;
+
+ function __construct($databaseId = null, $guid = null, $type = null,
$xml = null) {
+ //$this->databaseId = (is_null($databaseId)) ? null :
(int)$databaseId;
+ $this->databaseId = (int)$databaseId;
+ $this->guid = $guid;
+ $this->type = $type;
+ $this->xml = $xml;
+ }
+ function getDatabaseId() {
+ return $this->databaseId;
+ }
+ function getGuid() {
+ return $this->guid;
+ }
+ function getType() {
+ return $this->type;
+ }
+ function getXml() {
+ return $this->xml;
+ }
+
+ }
Added: branches/dev-bim2/property/tests/BIM/PropertyBimTestSuite.php
===================================================================
--- branches/dev-bim2/property/tests/BIM/PropertyBimTestSuite.php
(rev 0)
+++ branches/dev-bim2/property/tests/BIM/PropertyBimTestSuite.php
2010-12-23 15:45:51 UTC (rev 6698)
@@ -0,0 +1,89 @@
+<?php
+
+
+/*
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('PHPGW_API_UNIT_TEST_PATH', dirname(__FILE__));
+
+
+
+class phpGroupWareTestSuite extends PHPUnit_Framework_TestSuite
+{
+ protected static $login = 'peturbjorn';
+
+ // this is is a bit of a hack, but it should work
+ protected static $sessionid = '';
+
+ /**
+ * @protected array $suite_tests the tests which are part of this test
suite
+ */
+ //protected static $file1 =
dirname("TestCustomFields.php").'TestCustomFields.php';
+
+ protected static $suite_tests = array
+ (
+ // 'TestCustomFunctions.php',
+ 'TestCustomFields.php'
+ );
+
+ /**
+ * Get the list of tests for the suite
+ *
+ * @return object Test Suite to be run
+ */
+ public static function suite()
+ {
+ $suite = new phpGroupWareTestSuite();
+
+ //$suite->addTestFiles(self::$suite_tests);
+
+ $suite_tests = array(dirname(__FILE__).'\TestSObim.php');
+ $suite->addTestFiles($suite_tests);
+ return $suite;
+ }
+
+ /**
+ * Prepare the environment for the test suite to run
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ $GLOBALS['phpgw_info']['flags'] = array
+ (
+ 'currentapp' => 'login',
+ 'login' => true,
+ 'noapi' => false,
+ 'noheader' => true
+ );
+
+ $header = realpath(PHPGW_API_UNIT_TEST_PATH . '/../../..')
+ . '/header.inc.php';
+ include_once $header;
+
+ self::$sessionid = $GLOBALS['phpgw']->session->create(self::$login,
+ '', false);
+ }
+
+ /**
+ * Clean up the environment after running the test suite
+ *
+ * @return void
+ */
+ public function tearDown()
+ {
+ $GLOBALS['phpgw']->session->destroy(self::$sessionid);
+ }
+}
Added: branches/dev-bim2/property/tests/BIM/TestSObim.php
===================================================================
--- branches/dev-bim2/property/tests/BIM/TestSObim.php
(rev 0)
+++ branches/dev-bim2/property/tests/BIM/TestSObim.php 2010-12-23 15:45:51 UTC
(rev 6698)
@@ -0,0 +1,212 @@
+<?php
+
+
+
+/*
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+
+
+
+include('..\..\inc\class.sobim.inc.php');
+
+
+class TestSOnotes extends PHPUnit_Framework_TestCase
+{
+ private $bimTypeTableName = 'fm_bim_type';
+ private $bimItemTableName = 'fm_bim_data';
+ private $projectGuid;
+ private $projectType= 'ifcprojecttest';
+ private $projectXml;
+ private $buildingStorey1Guid;
+ private $buildingStorey2Guid;
+ private $buildingStorey1Type;
+ private $buildingStorey2Type;
+ private $buildingStorey1xml;
+ private $buildingStorey2xml;
+ private $db;
+
+ private $testBimObjectType = "testType010101";
+ /**
+ * @var boolean $backupGlobals disable backup of GLOBALS which breaks
things
+ */
+ protected $backupGlobals = false;
+
+ /**
+ * @var integer $fieldID The attribute ID used for all the tests
+ */
+ protected $fieldID;
+
+ protected static $addedNoteId=0;
+ protected $noteContent = "My dummy note content";
+ protected $editedNoteContent = "My edited dummy note content";
+
+ /**
+ * Setup the environment for the tests
+ *
+ * @return void
+ */
+ protected function setUp()
+ {
+ $GLOBALS['phpgw_info']['user']['account_id'] = 7;
+ $GLOBALS['phpgw']->acl->set_account_id(7); // not sure why this
is needed...
+ $this->db = & $GLOBALS['phpgw']->db;
+ $this->loadXmlVariables();
+ $this->addTestTypes();
+ $this->addTestItems();
+ }
+ /**
+ * Clean up the environment after running a test
+ *
+ * @return void
+ */
+ protected function tearDown()
+ {
+ //$this->removeTestItems();
+ //$this->removeTestTypes();
+
+ }
+
+ private function loadXmlVariables() {
+ $xml = simplexml_load_file('testData.xml');
+ $this->projectXml = $xml->project;
+ $this->projectGuid = $this->projectXml->attributes->guid."++";
//add ++ just in case the test data is in use
+ $this->projectType = $this->projectXml['ifcObjectType']."_test";
+
+ $this->buildingStorey1xml =
$xml->buildingStoreys->buildingStorey[0];
+ $this->buildingStorey1Guid =
$this->buildingStorey1xml->attributes->guid."++";
+ $this->buildingStorey1Type
=$this->buildingStorey1xml['ifcObjectType']."_test";
+
+ $this->buildingStorey2xml =
$xml->buildingStoreys->buildingStorey[1];
+ $this->buildingStorey2Guid =
$this->buildingStorey2xml->attributes->guid."++";
+ $this->buildingStorey2Type
=$this->buildingStorey2xml['ifcObjectType']."_test";
+
+ //echo $this->projectXml->();
+ }
+
+
+ public function testDb(){
+ $this->assertNotNull($this->db);
+ }
+
+ public function testGetAll() {
+ $sobim = new sobim_impl($this->db);
+ $bimItems = $sobim->getAll();
+ $this->assertEquals(3, count($bimItems));
+ foreach($bimItems as $bimItem) {
+ /* @var $bimItem BimItem */
+ $this->assertTrue(strlen($bimItem->getType()) > 0);
+ $this->assertTrue(strlen($bimItem->getDatabaseId()) >
0);
+ $this->assertTrue(strlen($bimItem->getGuid()) > 0);
+ $this->assertTrue(strlen($bimItem->getXml()) > 0);
+
+ }
+ }
+ public function testAddBimObjectTypeWithoutDescription() {
+ $sobim = new sobim_impl($this->db);
+
$this->assertTrue($sobim->addBimObjectType($this->testBimObjectType));
+ }
+ private function addTestItems() {
+ if($this->checkIfItemsAlreadyExist()) {
+ //throw new Exception('At least one item already exists
in database');
+ echo "exist";
+ } else {
+ echo "no";
+ $this->insertTestItem($this->projectXml->asXML(),
$this->projectType, $this->projectGuid);
+
$this->insertTestItem($this->buildingStorey1xml->asXML(),
$this->buildingStorey1Type, $this->buildingStorey1Guid);
+
$this->insertTestItem($this->buildingStorey2xml->asXML(),
$this->buildingStorey2Type, $this->buildingStorey2Guid);
+ }
+ }
+ private function removeTestItems() {
+ if($this->checkIfItemsAlreadyExist()) {
+ $this->removeTestItem($this->projectGuid);
+ $this->removeTestItem($this->buildingStorey1Guid);
+ $this->removeTestItem($this->buildingStorey2Guid);
+ }
+ }
+ private function removeTestItem($guid) {
+ $sql = "DELETE FROM $this->bimItemTableName where guid='$guid'";
+ $this->db->query($sql);
+ }
+ private function insertTestItem($itemXml, $itemType, $itemGuid) {
+ $itemXml = $this->db->db_addslashes($itemXml);
+ $sql = "INSERT INTO $this->bimItemTableName (type, guid,
xml_representation) values (";
+ $sql = $sql."(select id from $this->bimTypeTableName where name
= '$itemType'),";
+ $sql = $sql."'$itemGuid', '$itemXml')";
+ //echo $sql;
+ $this->db->query($sql,__LINE__,__FILE__);
+ }
+
+ private function addTestTypes() {
+ if($this->checkIfTestTypesAlreadyExist()) {
+ //throw new Exception('Test type already exists in
database!');
+ } else {
+ $this->insertTestType($this->buildingStorey1Type);
+ $this->insertTestType($this->projectType);
+ }
+ }
+ private function removeTestTypes() {
+ if($this->checkIfTestTypesAlreadyExist()) {
+ $this->removeTestType($this->buildingStorey1Type);
+ $this->removeTestType($this->projectType);
+ }
+ }
+ private function insertTestType($testTypeName) {
+ $sql = 'INSERT INTO '.$this->bimTypeTableName.' (name) VALUES
(\''.$testTypeName.'\')';
+ $this->db->query($sql);
+ }
+ private function removeTestType($testTypeName) {
+ $sql = "DELETE FROM ".$this->bimTypeTableName." where
name='".$testTypeName."'";
+ $this->db->query($sql);
+ }
+ private function checkIfItemsAlreadyExist() {
+ $resultAlias = 'test_item_count';
+ $sql = "SELECT count($this->bimItemTableName.id) as
$resultAlias from public.$this->bimItemTableName where ".
+ "guid = '$this->projectGuid' OR ".
+ "guid = '$this->buildingStorey1Guid' OR ".
+ "guid = '$this->buildingStorey2Guid'";
+
+ if(is_null($this->db->query($sql,__LINE__,__FILE__))) {
+ throw new Exception('Query to check items was
unsuccessful');
+ } else {
+ $this->db->next_record();
+ $rowCountOfItemTypes = $this->db->f($resultAlias);
+
+ if ( $rowCountOfItemTypes != 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private function checkIfTestTypesAlreadyExist() {
+ $resultAlias = 'test_type_count';
+ $sql = 'SELECT count('.$this->bimTypeTableName.'.id) as
'.$resultAlias.' FROM public.'.$this->bimTypeTableName.' WHERE
'.$this->bimTypeTableName.'.name = \''.$this->buildingStorey1Type.'\' OR
'.$this->bimTypeTableName.'.name = \''.$this->projectType.'\'';
+ //echo "sql is ::".$sql."::";
+ $q = $this->db->query($sql);
+ if(is_null($q)) {
+ throw new Exception('Query to check types was
unsuccessful');
+ }
+ $this->db->next_record();
+ $rowCountOfTestTypes = $this->db->f($resultAlias);
+ if ( $rowCountOfTestTypes != 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
Added: branches/dev-bim2/property/tests/BIM/testData.xml
===================================================================
--- branches/dev-bim2/property/tests/BIM/testData.xml
(rev 0)
+++ branches/dev-bim2/property/tests/BIM/testData.xml 2010-12-23 15:45:51 UTC
(rev 6698)
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<wholeModelOutput>
+ <project ifcObjectType="ifcproject">
+ <attributes>
+ <guid>3KFKb0sfrDJwSHalGIQFZT</guid>
+ <longName>FM Architectural Handover</longName>
+ <name>FM-A-01</name>
+ </attributes>
+ <ownerHistory>
+ <changeAction>ADDED</changeAction>
+ <creationDate>1179073813</creationDate>
+ <owningApplication>
+ <applicationDeveloper>
+ <name>AEC3</name>
+ </applicationDeveloper>
+ <applicationFullName>IFC text editor</applicationFullName>
+ <applicationIdentifier>IFCtext</applicationIdentifier>
+ <version>Version 1</version>
+ </owningApplication>
+ <owningUser>
+ <organization>
+ <name>AEC3</name>
+ </organization>
+ <person>
+ <familyName>Liebich</familyName>
+ <givenName>Thomas</givenName>
+ </person>
+ </owningUser>
+ </ownerHistory>
+ <decomposition>
+ <buildings>
+ <guid>28hfXoRX9EMhvGvGhmaaae</guid>
+ </buildings>
+ <site>28hfXoRX9EMhvGvGhmaaad</site>
+ </decomposition>
+ <units>
+ <entry>
+ <key>Volume</key>
+ <value>CUBIC_METRE</value>
+ </entry>
+ <entry>
+ <key>Length</key>
+ <value>METRE</value>
+ </entry>
+ <entry>
+ <key>Area</key>
+ <value>SQUARE_METRE</value>
+ </entry>
+ </units>
+ </project>
+ <buildingStoreys>
+ <buildingStorey ifcObjectType="ifcbuildingstorey">
+ <attributes>
+ <description>Kellergeschoss</description>
+ <elevation>-3.2</elevation>
+ <guid>0h$ksovXH3Jeg0w$H7aaaf</guid>
+ <longName>KG</longName>
+ <name>A.-1</name>
+ </attributes>
+ <ownerHistory>
+ <changeAction>ADDED</changeAction>
+ <creationDate>1179073813</creationDate>
+ <owningApplication>
+ <applicationDeveloper>
+ <name>AEC3</name>
+ </applicationDeveloper>
+ <applicationFullName>IFC text editor</applicationFullName>
+ <applicationIdentifier>IFCtext</applicationIdentifier>
+ <version>Version 1</version>
+ </owningApplication>
+ <owningUser>
+ <organization>
+ <name>AEC3</name>
+ </organization>
+ <person>
+ <familyName>Liebich</familyName>
+ <givenName>Thomas</givenName>
+ </person>
+ </owningUser>
+ </ownerHistory>
+ <spatialDecomposition>
+ <buildings>
+ <guid>28hfXoRX9EMhvGvGhmaaae</guid>
+ </buildings>
+ <project>3KFKb0sfrDJwSHalGIQFZT</project>
+ <site>28hfXoRX9EMhvGvGhmaaad</site>
+ </spatialDecomposition>
+ </buildingStorey>
+ <buildingStorey ifcObjectType="ifcbuildingstorey">
+ <attributes>
+ <elevation>0.0</elevation>
+ <guid>0h$ksovXH3Jeg0w$H7yFJf</guid>
+ <longName>EG</longName>
+ <name>A.0</name>
+ </attributes>
+ <baseQuantities>
+ <quantity>
+ <name>GrossHeight</name>
+ <value>3.2</value>
+ </quantity>
+ <quantity>
+ <name>NetHeight</name>
+ <value>3.0</value>
+ </quantity>
+ </baseQuantities>
+ <ownerHistory>
+ <changeAction>ADDED</changeAction>
+ <creationDate>1179073813</creationDate>
+ <owningApplication>
+ <applicationDeveloper>
+ <name>AEC3</name>
+ </applicationDeveloper>
+ <applicationFullName>IFC text editor</applicationFullName>
+ <applicationIdentifier>IFCtext</applicationIdentifier>
+ <version>Version 1</version>
+ </owningApplication>
+ <owningUser>
+ <organization>
+ <name>AEC3</name>
+ </organization>
+ <person>
+ <familyName>Liebich</familyName>
+ <givenName>Thomas</givenName>
+ </person>
+ </owningUser>
+ </ownerHistory>
+ <properties>
+ <propertySet name="Common property">
+ <property>
+ <name>EntranceLevel</name>
+ <value>IFCBOOLEAN(.T.)</value>
+ </property>
+ <property>
+ <name>AboveGround</name>
+ <value>IFCLOGICAL(.T.)</value>
+ </property>
+ </propertySet>
+ </properties>
+ <spatialDecomposition>
+ <buildings>
+ <guid>28hfXoRX9EMhvGvGhmaaae</guid>
+ </buildings>
+ <project>3KFKb0sfrDJwSHalGIQFZT</project>
+ <site>28hfXoRX9EMhvGvGhmaaad</site>
+ <spaces>
+ <guid>0h$ksovXH3Jeg0w$H724af</guid>
+ <guid>0h$ksovXH3Jeg0w$H7s8af</guid>
+ <guid>0h$ksovXH3Je2d9dH7s8af</guid>
+ <guid>0h$ksovXH3Jeg02dH7s8af</guid>
+ </spaces>
+ </spatialDecomposition>
+ </buildingStorey>
+ </buildingStoreys>
+</wholeModelOutput>
+
\ No newline at end of file
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Fmsystem-commits] [6698] Added sobim object, along with unit tests,
Petur Bjorn Thorsteinsson <=