src/core/net/sf/basedb/util/overview/validator/AnnotationValidator.java

Code
Comments
Other
Rev Date Author Line
4740 05 Feb 09 nicklas 1 /**
4740 05 Feb 09 nicklas 2   $Id$
4740 05 Feb 09 nicklas 3
4740 05 Feb 09 nicklas 4   Copyright (C) 2008 Nicklas Nordborg
4740 05 Feb 09 nicklas 5
4740 05 Feb 09 nicklas 6   This file is part of BASE - BioArray Software Environment.
4740 05 Feb 09 nicklas 7   Available at http://base.thep.lu.se/
4740 05 Feb 09 nicklas 8
4740 05 Feb 09 nicklas 9   BASE is free software; you can redistribute it and/or
4740 05 Feb 09 nicklas 10   modify it under the terms of the GNU General Public License
4740 05 Feb 09 nicklas 11   as published by the Free Software Foundation; either version 3
4740 05 Feb 09 nicklas 12   of the License, or (at your option) any later version.
4740 05 Feb 09 nicklas 13
4740 05 Feb 09 nicklas 14   BASE is distributed in the hope that it will be useful,
4740 05 Feb 09 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
4740 05 Feb 09 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4740 05 Feb 09 nicklas 17   GNU General Public License for more details.
4740 05 Feb 09 nicklas 18
4740 05 Feb 09 nicklas 19   You should have received a copy of the GNU General Public License
4740 05 Feb 09 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
4740 05 Feb 09 nicklas 21 */
4740 05 Feb 09 nicklas 22 package net.sf.basedb.util.overview.validator;
4740 05 Feb 09 nicklas 23
6939 21 Aug 15 nicklas 24 import java.util.HashMap;
4743 09 Feb 09 nicklas 25 import java.util.HashSet;
4743 09 Feb 09 nicklas 26 import java.util.List;
6939 21 Aug 15 nicklas 27 import java.util.Map;
4743 09 Feb 09 nicklas 28 import java.util.Set;
4743 09 Feb 09 nicklas 29
4740 05 Feb 09 nicklas 30 import net.sf.basedb.core.Annotatable;
4740 05 Feb 09 nicklas 31 import net.sf.basedb.core.Annotation;
4743 09 Feb 09 nicklas 32 import net.sf.basedb.core.AnnotationType;
4743 09 Feb 09 nicklas 33 import net.sf.basedb.core.BasicItem;
4740 05 Feb 09 nicklas 34 import net.sf.basedb.core.DbControl;
4761 16 Feb 09 nicklas 35 import net.sf.basedb.core.Experiment;
4743 09 Feb 09 nicklas 36 import net.sf.basedb.core.InvalidDataException;
4743 09 Feb 09 nicklas 37 import net.sf.basedb.core.Item;
4761 16 Feb 09 nicklas 38 import net.sf.basedb.core.ItemQuery;
4761 16 Feb 09 nicklas 39 import net.sf.basedb.core.ItemResultIterator;
4743 09 Feb 09 nicklas 40 import net.sf.basedb.core.PermissionDeniedException;
5132 14 Oct 09 nicklas 41 import net.sf.basedb.core.snapshot.AnnotationFilter;
5132 14 Oct 09 nicklas 42 import net.sf.basedb.core.snapshot.AnnotationSnapshot;
5132 14 Oct 09 nicklas 43 import net.sf.basedb.core.snapshot.SnapshotManager;
4740 05 Feb 09 nicklas 44 import net.sf.basedb.util.overview.Fix;
4740 05 Feb 09 nicklas 45 import net.sf.basedb.util.overview.OverviewContext;
4740 05 Feb 09 nicklas 46 import net.sf.basedb.util.overview.Node;
4743 09 Feb 09 nicklas 47 import net.sf.basedb.util.overview.OverviewUtil;
4743 09 Feb 09 nicklas 48 import net.sf.basedb.util.overview.Validator;
4761 16 Feb 09 nicklas 49 import net.sf.basedb.util.overview.filter.ItemTypeFilter;
7860 20 Oct 20 nicklas 50 import net.sf.basedb.util.overview.loader.AnnotationLoader;
4740 05 Feb 09 nicklas 51
4740 05 Feb 09 nicklas 52 /**
4740 05 Feb 09 nicklas 53   Validator implementation for annotations. The node containing
4740 05 Feb 09 nicklas 54   the annotations is expected to live in a folder-type node that
4740 05 Feb 09 nicklas 55   has an {@link Annotatable} item as it's parent. 
4740 05 Feb 09 nicklas 56   Validation rules:
4740 05 Feb 09 nicklas 57   <ul>
4743 09 Feb 09 nicklas 58   <li>Annotation is a protocol parameter: {@link Validator#ANNOTATION_IS_PARAMETER}
4743 09 Feb 09 nicklas 59   <li>Annotation is not valid for item type: {@link Validator#ANNOTATION_INVALID_ITEM}
4743 09 Feb 09 nicklas 60   <li>Annotation has invalid value: {@link Validator#ANNOTATION_INVALID_VALUE}
6024 23 Mar 12 nicklas 61   <li>Annotation has too many values: {@link Validator#ANNOTATION_TOO_MANY_VALUES}
4743 09 Feb 09 nicklas 62   <li>Access denied to annotation type: {@link Validator#DENIED_ANNOTATIONTYPE}
4743 09 Feb 09 nicklas 63   <li>Annotation is inherited from non-parent item: {@link Validator#ANNOTATION_INHERIT_FROM_NONPARENT}
4743 09 Feb 09 nicklas 64   <li>Multiple annotations of same annotation type: {@link Validator#ANNOTATION_INHERIT_MULTIPLE}
4747 10 Feb 09 nicklas 65   <li>Missing a 'Required by MIAME' annotation: {@link Validator#MISSING_MIAME_ANNOTATION}
4761 16 Feb 09 nicklas 66   <li>Missing experimental factor value: {@link Validator#MISSING_FACTORVALUE}
4740 05 Feb 09 nicklas 67   </ul>
4740 05 Feb 09 nicklas 68   
4740 05 Feb 09 nicklas 69   @author Nicklas
4740 05 Feb 09 nicklas 70   @version 2.10
4740 05 Feb 09 nicklas 71   @base.modified $Date$
4740 05 Feb 09 nicklas 72 */
4740 05 Feb 09 nicklas 73 public class AnnotationValidator
4740 05 Feb 09 nicklas 74   extends BasicNodeValidator<Annotation>
4740 05 Feb 09 nicklas 75 {
4740 05 Feb 09 nicklas 76   
4740 05 Feb 09 nicklas 77   public AnnotationValidator()
4740 05 Feb 09 nicklas 78   {
4740 05 Feb 09 nicklas 79     super(null, null);
4740 05 Feb 09 nicklas 80   }
4740 05 Feb 09 nicklas 81   
4740 05 Feb 09 nicklas 82   /*
4740 05 Feb 09 nicklas 83     From BasicNodeValidator class
4740 05 Feb 09 nicklas 84     -----------------------------
4740 05 Feb 09 nicklas 85   */
4740 05 Feb 09 nicklas 86   @Override
4740 05 Feb 09 nicklas 87   public void postValidate(DbControl dc, OverviewContext context, Node node, Node parentNode)
4740 05 Feb 09 nicklas 88   {
4740 05 Feb 09 nicklas 89     super.postValidate(dc, context, node, parentNode);
4743 09 Feb 09 nicklas 90     if (parentNode.getNodeType() == Node.Type.FOLDER) parentNode = parentNode.getParent();
7860 20 Oct 20 nicklas 91     Annotatable parent = AnnotationLoader.getAnnotatableFromNode(dc, parentNode);
7860 20 Oct 20 nicklas 92     if (parent == null) return;
4747 10 Feb 09 nicklas 93     
7860 20 Oct 20 nicklas 94     Item parentItemType = parent.getType();
7860 20 Oct 20 nicklas 95     
5807 14 Oct 11 nicklas 96     Annotation a = (Annotation)node.getItem(dc);
5132 14 Oct 09 nicklas 97     SnapshotManager manager = context.getSnapshotManager();
5132 14 Oct 09 nicklas 98     // The primary use of the snapshot is to load the values if the annotation is a primary annotation
5132 14 Oct 09 nicklas 99     // Thus, we do not search inherited annotations
5132 14 Oct 09 nicklas 100     List<AnnotationSnapshot> snapshots = manager.findAnnotations(dc, parent, new AnnotationFilter(a), false);
5132 14 Oct 09 nicklas 101     AnnotationSnapshot snapshot = snapshots.size() > 0 ? snapshots.get(0) : null;
6939 21 Aug 15 nicklas 102     boolean isPrimary = snapshot != null && snapshot.getSource() == Annotation.Source.PRIMARY;
4747 10 Feb 09 nicklas 103
4743 09 Feb 09 nicklas 104     AnnotationType at = null;
4743 09 Feb 09 nicklas 105     try
4743 09 Feb 09 nicklas 106     {
4743 09 Feb 09 nicklas 107       at = a.getAnnotationType();
4743 09 Feb 09 nicklas 108       
4747 10 Feb 09 nicklas 109       // Check that the (primary) annotation is not a protocol parameter
4747 10 Feb 09 nicklas 110       if (isPrimary && at.isProtocolParameter())
4743 09 Feb 09 nicklas 111       {
4757 13 Feb 09 nicklas 112         context.createFailure(Validator.ANNOTATION_IS_PARAMETER, node, 
4743 09 Feb 09 nicklas 113           "Annotation '" + at.getName() + "' is a protocol parameter",
6939 21 Aug 15 nicklas 114           new Fix("Remove annotation value", (BasicItem)parent, a),
4743 09 Feb 09 nicklas 115           new Fix("Change 'parameter' flag of annotation type", at)
4743 09 Feb 09 nicklas 116         );
4743 09 Feb 09 nicklas 117       }
4740 05 Feb 09 nicklas 118
4747 10 Feb 09 nicklas 119       // Check if the (primary) annotation can be used on this item type
4747 10 Feb 09 nicklas 120       if (isPrimary && !at.isEnabledForItem(parentItemType))
4743 09 Feb 09 nicklas 121       {
4757 13 Feb 09 nicklas 122         context.createFailure(Validator.ANNOTATION_INVALID_ITEM, node, 
4743 09 Feb 09 nicklas 123           "Annotation '" + at.getName() + "' is invalid for item type: " + parentItemType, 
6939 21 Aug 15 nicklas 124           new Fix("Remove annotation value", (BasicItem)parent, a),
4743 09 Feb 09 nicklas 125           new Fix("Add '" + parentItemType.name() + "' to annotation type", at)
4743 09 Feb 09 nicklas 126         );
4743 09 Feb 09 nicklas 127       }
4743 09 Feb 09 nicklas 128
4747 10 Feb 09 nicklas 129       // Check that the (primary) annotation values are valid
4747 10 Feb 09 nicklas 130       if (isPrimary)
4743 09 Feb 09 nicklas 131       {
4747 10 Feb 09 nicklas 132         try
4743 09 Feb 09 nicklas 133         {
6939 21 Aug 15 nicklas 134           List<?> values = snapshot.getActualValues();
4747 10 Feb 09 nicklas 135           if (values != null)
4743 09 Feb 09 nicklas 136           {
6024 23 Mar 12 nicklas 137             if (values.size() > at.getMultiplicity())
6024 23 Mar 12 nicklas 138             {
6024 23 Mar 12 nicklas 139               context.createFailure(Validator.ANNOTATION_TOO_MANY_VALUES, node, 
6024 23 Mar 12 nicklas 140                 "Annotation '" + at.getName() +  "' have " + values.size() + 
6024 23 Mar 12 nicklas 141                 " values but only " + at.getMultiplicity() + " are allowed",
6939 21 Aug 15 nicklas 142                 new Fix("Remove annotation values", (BasicItem)parent, a),
6024 23 Mar 12 nicklas 143                 new Fix("Increase the 'multiplity' option for the annotation type", at)
6024 23 Mar 12 nicklas 144               );
6024 23 Mar 12 nicklas 145             }
6024 23 Mar 12 nicklas 146             
4747 10 Feb 09 nicklas 147             for (Object value : values)
4747 10 Feb 09 nicklas 148             {
4747 10 Feb 09 nicklas 149               at.validateAnnotationValue(value);
4747 10 Feb 09 nicklas 150             }
4743 09 Feb 09 nicklas 151           }
4743 09 Feb 09 nicklas 152         }
4747 10 Feb 09 nicklas 153         catch (InvalidDataException ex)
4747 10 Feb 09 nicklas 154         {
4757 13 Feb 09 nicklas 155           context.createFailure(Validator.ANNOTATION_INVALID_VALUE, node, ex.getMessage(),
6939 21 Aug 15 nicklas 156             new Fix("Change annotation value", (BasicItem)parent, a),
4747 10 Feb 09 nicklas 157             new Fix("Change annotation type restrictions", at)
4747 10 Feb 09 nicklas 158           );
4747 10 Feb 09 nicklas 159         }
4743 09 Feb 09 nicklas 160       }
4743 09 Feb 09 nicklas 161     }
4743 09 Feb 09 nicklas 162     catch (PermissionDeniedException ex)
4740 05 Feb 09 nicklas 163     {
4743 09 Feb 09 nicklas 164       // When the user doesn't have access to the annotation type 
4743 09 Feb 09 nicklas 165       // of the annotation we can't validate or check other things
4743 09 Feb 09 nicklas 166       context.createFailure(Validator.DENIED_ANNOTATIONTYPE, node, null);
4740 05 Feb 09 nicklas 167     }
4743 09 Feb 09 nicklas 168
4743 09 Feb 09 nicklas 169     // If the annotation is inherited from a 'parent item', verify that 
6939 21 Aug 15 nicklas 170     // it is a reachable/existing parent
4747 10 Feb 09 nicklas 171     if (!isPrimary)
4740 05 Feb 09 nicklas 172     {
4752 11 Feb 09 nicklas 173       Set<Annotatable> reachableParents = OverviewUtil.getAnnotatableParents(dc, context, parent);
4752 11 Feb 09 nicklas 174       try
4743 09 Feb 09 nicklas 175       {
6939 21 Aug 15 nicklas 176         Annotation inherited = a.getInheritedFrom();
6939 21 Aug 15 nicklas 177         if (inherited == null)
4752 11 Feb 09 nicklas 178         {
6939 21 Aug 15 nicklas 179           context.createFailure(Validator.ANNOTATION_CLONED_FROM_UNKNOWNPARENT, node, 
6939 21 Aug 15 nicklas 180             "Cloned annotation '" + (at != null ? 
6939 21 Aug 15 nicklas 181             at.getName() : a.getValueType().toString()) + "' from unknown parent",
6939 21 Aug 15 nicklas 182             new Fix("Clone from another item", (BasicItem)parent, a)
6939 21 Aug 15 nicklas 183           );
6939 21 Aug 15 nicklas 184         }
6939 21 Aug 15 nicklas 185         else
6939 21 Aug 15 nicklas 186         {
6939 21 Aug 15 nicklas 187           Annotatable inheritedFrom = inherited.getAnnotationSet().getItem();
6939 21 Aug 15 nicklas 188           if (!reachableParents.contains(inheritedFrom))
6939 21 Aug 15 nicklas 189           {
6939 21 Aug 15 nicklas 190             context.createFailure(Validator.ANNOTATION_INHERIT_FROM_NONPARENT, node, 
4752 11 Feb 09 nicklas 191               "Inheriting annotation '" + (at != null ? 
6939 21 Aug 15 nicklas 192               at.getName() : a.getValueType().toString()) + "' from non-parent",
6939 21 Aug 15 nicklas 193               new Fix("Inherit from another item", (BasicItem)parent, a)
4752 11 Feb 09 nicklas 194             );
6939 21 Aug 15 nicklas 195           }
6960 01 Oct 15 nicklas 196           
6960 01 Oct 15 nicklas 197           // Check if a cloned annotation is out-of-sync
6960 01 Oct 15 nicklas 198           if (a.getSource() == Annotation.Source.CLONED)
6960 01 Oct 15 nicklas 199           {
6960 01 Oct 15 nicklas 200             if (a.getLastUpdate().before(inherited.getLastUpdate()))
6960 01 Oct 15 nicklas 201             {
6960 01 Oct 15 nicklas 202               context.createFailure(Validator.ANNOTATION_CLONE_OUTOFSYNC, node, 
6960 01 Oct 15 nicklas 203                 "Cloned annotation '" + (at != null ? 
6960 01 Oct 15 nicklas 204                 at.getName() : a.getValueType().toString()) + "' is out-of-sync with source annotation",
6960 01 Oct 15 nicklas 205                 new Fix("Sync cloned annotation", (BasicItem)parent, a)
6960 01 Oct 15 nicklas 206               );
6960 01 Oct 15 nicklas 207             }
6960 01 Oct 15 nicklas 208           }
4752 11 Feb 09 nicklas 209         }
4752 11 Feb 09 nicklas 210       }
4752 11 Feb 09 nicklas 211       catch (PermissionDeniedException ex)
4752 11 Feb 09 nicklas 212       {
4757 13 Feb 09 nicklas 213         context.createFailure(Validator.ANNOTATION_INHERIT_FROM_DENIEDPARENT, node, 
4743 09 Feb 09 nicklas 214             "Inheriting annotation '" + (at != null ? 
4752 11 Feb 09 nicklas 215               at.getName() : a.getValueType().toString()) + "' from denied parent",
6939 21 Aug 15 nicklas 216             new Fix("Inherit from another item", (BasicItem)parent, a)
4743 09 Feb 09 nicklas 217           );
4743 09 Feb 09 nicklas 218       }
4740 05 Feb 09 nicklas 219     }
4743 09 Feb 09 nicklas 220   }
4743 09 Feb 09 nicklas 221   
4743 09 Feb 09 nicklas 222   @Override
4743 09 Feb 09 nicklas 223   public void postValidateFolder(DbControl dc, OverviewContext context, Node folderNode, Node parentNode) 
4743 09 Feb 09 nicklas 224   {
4743 09 Feb 09 nicklas 225     super.postValidateFolder(dc, context, folderNode, parentNode);
7860 20 Oct 20 nicklas 226     Annotatable parent = AnnotationLoader.getAnnotatableFromNode(dc, parentNode);
7860 20 Oct 20 nicklas 227     if (parent == null) return;
7860 20 Oct 20 nicklas 228     Item parentItemType = parent.getType();
4743 09 Feb 09 nicklas 229     
4743 09 Feb 09 nicklas 230     // Check for 'Required by MIAME' annotations and duplicates
7860 20 Oct 20 nicklas 231     Set<AnnotationType> miame = OverviewUtil.getMiameAnnotationTypes(dc, context, parentItemType);
4743 09 Feb 09 nicklas 232     Set<AnnotationType> all = new HashSet<AnnotationType>();
6939 21 Aug 15 nicklas 233     Map<AnnotationType, Annotation> duplicates = new HashMap<AnnotationType, Annotation>();
4743 09 Feb 09 nicklas 234     if (folderNode != null && folderNode.numChildren() > 0)
4740 05 Feb 09 nicklas 235     {
4743 09 Feb 09 nicklas 236       for (Node child : folderNode.getChildren())
4740 05 Feb 09 nicklas 237       {
5807 14 Oct 11 nicklas 238         Annotation a = (Annotation)child.getItem(dc);
4743 09 Feb 09 nicklas 239         try
4740 05 Feb 09 nicklas 240         {
4743 09 Feb 09 nicklas 241           AnnotationType at = a.getAnnotationType();
6939 21 Aug 15 nicklas 242           if (!all.add(at)) duplicates.put(at, a);
4740 05 Feb 09 nicklas 243         }
4743 09 Feb 09 nicklas 244         catch (PermissionDeniedException ex)
4743 09 Feb 09 nicklas 245         {}
4740 05 Feb 09 nicklas 246       }
4740 05 Feb 09 nicklas 247     }
4743 09 Feb 09 nicklas 248     
4743 09 Feb 09 nicklas 249     // Report duplicate annotation types
4743 09 Feb 09 nicklas 250     if (duplicates.size() > 0)
4740 05 Feb 09 nicklas 251     {
6939 21 Aug 15 nicklas 252       for (Map.Entry<AnnotationType, Annotation> dup : duplicates.entrySet())
4743 09 Feb 09 nicklas 253       {
4743 09 Feb 09 nicklas 254         context.createFailure(Validator.ANNOTATION_INHERIT_MULTIPLE, parentNode, 
6939 21 Aug 15 nicklas 255           "Duplicate annotations of type '" + dup.getKey().getName() + "'",
7860 20 Oct 20 nicklas 256           new Fix("Modify annotations", (BasicItem)parent, dup.getValue())
4743 09 Feb 09 nicklas 257         );
4743 09 Feb 09 nicklas 258       }
4740 05 Feb 09 nicklas 259     }
4743 09 Feb 09 nicklas 260
4743 09 Feb 09 nicklas 261     // Report missing 'Required by MIAME' annotations
4743 09 Feb 09 nicklas 262     if (miame != null && miame.size() > 0)
4743 09 Feb 09 nicklas 263     {
4743 09 Feb 09 nicklas 264       for (AnnotationType at : miame)
4743 09 Feb 09 nicklas 265       {
4743 09 Feb 09 nicklas 266         if (!all.contains(at))
4743 09 Feb 09 nicklas 267         {
4747 10 Feb 09 nicklas 268           context.createFailure(Validator.MISSING_MIAME_ANNOTATION, parentNode, 
4747 10 Feb 09 nicklas 269               "No value for annotation '" + at.getName() + "' (required fo MIAME)", 
4743 09 Feb 09 nicklas 270               new Fix("Add value for annotation '" + at.getName() + "'", (BasicItem)parent, at, false),
4743 09 Feb 09 nicklas 271               new Fix("Remove 'Required for MIAME' flag on '" + at.getName() + "'", at)
4743 09 Feb 09 nicklas 272             );
4743 09 Feb 09 nicklas 273         }
4743 09 Feb 09 nicklas 274       }
4743 09 Feb 09 nicklas 275     }
4761 16 Feb 09 nicklas 276     
4761 16 Feb 09 nicklas 277     // Check for missing experimental factor values (if we have an experiment in the parent path)
6959 01 Oct 15 nicklas 278     if (parentNode.getItemType() == Item.ROOTRAWBIOASSAY)
4761 16 Feb 09 nicklas 279     {
4761 16 Feb 09 nicklas 280       Node experimentNode = parentNode.getFirstParent(new ItemTypeFilter(Item.EXPERIMENT));
4761 16 Feb 09 nicklas 281       if (experimentNode != null)
4761 16 Feb 09 nicklas 282       {
4761 16 Feb 09 nicklas 283         Experiment exp = (Experiment)experimentNode.getItem(dc);
4761 16 Feb 09 nicklas 284         ItemQuery<AnnotationType> query = context.initQuery(exp.getExperimentalFactors());
4761 16 Feb 09 nicklas 285         ItemResultIterator<AnnotationType> it = query.iterate(dc);
4761 16 Feb 09 nicklas 286         while (it.hasNext())
4761 16 Feb 09 nicklas 287         {
4761 16 Feb 09 nicklas 288           AnnotationType factor = it.next();
4761 16 Feb 09 nicklas 289           if (!all.contains(factor))
4761 16 Feb 09 nicklas 290           {
6959 01 Oct 15 nicklas 291             Fix fix = factor.isEnabledForItem(Item.ROOTRAWBIOASSAY) ?
4761 16 Feb 09 nicklas 292               new Fix("Add value to experimental factor", (BasicItem)parent, factor, false) :
4761 16 Feb 09 nicklas 293               new Fix("Inherit annotation from a parent item", (BasicItem)parent, factor, true);
4761 16 Feb 09 nicklas 294             context.createFailure(Validator.MISSING_FACTORVALUE, parentNode, 
4761 16 Feb 09 nicklas 295               "Missing value for experimental factor '" + factor.getName() + "'", fix);
4761 16 Feb 09 nicklas 296           }
4761 16 Feb 09 nicklas 297         }
4761 16 Feb 09 nicklas 298       }
4761 16 Feb 09 nicklas 299     }
4740 05 Feb 09 nicklas 300   }
4740 05 Feb 09 nicklas 301
4740 05 Feb 09 nicklas 302   /**
4740 05 Feb 09 nicklas 303     @return Always "null"
4740 05 Feb 09 nicklas 304   */
4740 05 Feb 09 nicklas 305   @Override
5651 08 Jun 11 nicklas 306   protected Fix getMissingItemFix(DbControl dc, Node parentNode)
4740 05 Feb 09 nicklas 307   {
4740 05 Feb 09 nicklas 308     return null;
4740 05 Feb 09 nicklas 309   }
4740 05 Feb 09 nicklas 310   // ----------------------------
4740 05 Feb 09 nicklas 311   
4740 05 Feb 09 nicklas 312 }