Index: /trunk/data/validator/geometry.mapcss
===================================================================
--- /trunk/data/validator/geometry.mapcss	(revision 6610)
+++ /trunk/data/validator/geometry.mapcss	(revision 6611)
@@ -10,2 +10,8 @@
   assertNoMatch: "node bridge=13";
 }
+
+/* Building inside building (spatial test) */
+*[building!~/no|entrance/][coalesce(tag("layer"),"0") = coalesce(parent_tag("layer"),"0")] ∈
+*[building!~/no|entrance/] {
+  throwWarning: tr("Building inside building");
+}
Index: /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 6611)
@@ -24,5 +24,4 @@
 import org.openstreetmap.josm.data.validation.tests.Addresses;
 import org.openstreetmap.josm.data.validation.tests.BarriersEntrances;
-import org.openstreetmap.josm.data.validation.tests.BuildingInBuilding;
 import org.openstreetmap.josm.data.validation.tests.Coastlines;
 import org.openstreetmap.josm.data.validation.tests.ConditionalKeys;
@@ -111,5 +110,4 @@
         TurnrestrictionTest.class, // ID  1801 ..  1899
         DuplicateRelation.class, // ID 1901 .. 1999
-        BuildingInBuilding.class, // ID 2001 .. 2099
         OverlappingAreas.class, // ID 2201 .. 2299
         WayConnectedToArea.class, // ID 2301 .. 2399
Index: unk/src/org/openstreetmap/josm/data/validation/tests/BuildingInBuilding.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/BuildingInBuilding.java	(revision 6610)
+++ 	(revision )
@@ -1,157 +1,0 @@
-// License: GPL. See LICENSE file for details.
-package org.openstreetmap.josm.data.validation.tests;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.QuadBuckets;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.validation.Severity;
-import org.openstreetmap.josm.data.validation.Test;
-import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.tools.FilteredCollection;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
-import org.openstreetmap.josm.tools.Predicate;
-
-/**
- * Checks for building areas inside of buildings
- * @since 4409
- */
-public class BuildingInBuilding extends Test {
-
-    protected static final int BUILDING_INSIDE_BUILDING = 2001;
-    private final List<OsmPrimitive> primitivesToCheck = new LinkedList<OsmPrimitive>();
-    private final QuadBuckets<Way> index = new QuadBuckets<Way>();
-
-    /**
-     * Constructs a new {@code BuildingInBuilding} test.
-     */
-    public BuildingInBuilding() {
-        super(tr("Building inside building"), tr("Checks for building areas inside of buildings."));
-    }
-
-    @Override
-    public void visit(Node n) {
-        if (n.isUsable() && isBuilding(n)) {
-            primitivesToCheck.add(n);
-        }
-    }
-
-    @Override
-    public void visit(Way w) {
-        if (w.isUsable() && w.isClosed() && isBuilding(w)) {
-            primitivesToCheck.add(w);
-            index.add(w);
-        }
-    }
-
-    @Override
-    public void visit(Relation r) {
-        if (r.isUsable() && r.isMultipolygon() && isBuilding(r)) {
-            primitivesToCheck.add(r);
-            for (RelationMember m : r.getMembers()) {
-                if (m.getRole().equals("outer") && m.getType().equals(OsmPrimitiveType.WAY)) {
-                    index.add(m.getWay());
-                }
-            }
-        }
-    }
-
-    private static boolean isInPolygon(Node n, List<Node> polygon) {
-        return Geometry.nodeInsidePolygon(n, polygon);
-    }
-
-    protected boolean sameLayers(Way w1, Way w2) {
-        String l1 = w1.get("layer") != null ? w1.get("layer") : "0";
-        String l2 = w2.get("layer") != null ? w2.get("layer") : "0";
-        return l1.equals(l2);
-    }
-
-    @Override
-    public void endTest() {
-        for (final OsmPrimitive p : primitivesToCheck) {
-            Collection<Way> outers = new FilteredCollection<Way>(index.search(p.getBBox()), new Predicate<Way>() {
-
-                protected boolean evaluateNode(Node n, Way object) {
-                    return isInPolygon(n, object.getNodes()) || object.getNodes().contains(n);
-                }
-
-                protected boolean evaluateWay(final Way w, Way object) {
-                    if (w.equals(object)) return false;
-
-                    // Get all multipolygons referencing object
-                    Collection<OsmPrimitive> buildingMultiPolygons = new FilteredCollection<OsmPrimitive>(object.getReferrers(), new Predicate<OsmPrimitive>() {
-                        @Override
-                        public boolean evaluate(OsmPrimitive object) {
-                            return primitivesToCheck.contains(object);
-                        }
-                    }) ;
-
-                    // if there's none, test if w is inside object
-                    if (buildingMultiPolygons.isEmpty()) {
-                        PolygonIntersection inter = Geometry.polygonIntersection(w.getNodes(), object.getNodes());
-                        // Final check on "layer" tag. Buildings of different layers may be superposed
-                        return (inter == PolygonIntersection.FIRST_INSIDE_SECOND || inter == PolygonIntersection.CROSSING) && sameLayers(w, object);
-                    } else {
-                        // Else, test if w is inside one of the multipolygons
-                        for (OsmPrimitive bmp : buildingMultiPolygons) {
-                            if (bmp instanceof Relation && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) bmp, new Predicate<Way>() {
-                                @Override
-                                public boolean evaluate(Way outer) {
-                                    return sameLayers(w, outer);
-                                }
-                            })) {
-                                return true;
-                            }
-                        }
-                        return false;
-                    }
-                }
-
-                protected boolean evaluateRelation(Relation r, Way object) {
-                    Geometry.MultiPolygonMembers mpm = new Geometry.MultiPolygonMembers((Relation) p);
-                    for (Way out : mpm.outers) {
-                        if (evaluateWay(out, object)) {
-                            return true;
-                        }
-                    }
-                    return false;
-                }
-
-                @Override
-                public boolean evaluate(Way object) {
-                    if (p.equals(object))
-                        return false;
-                    else if (p instanceof Node)
-                        return evaluateNode((Node) p, object);
-                    else if (p instanceof Way)
-                        return evaluateWay((Way) p, object);
-                    else if (p instanceof Relation)
-                        return evaluateRelation((Relation) p, object);
-                    return false;
-                }
-            });
-
-            if (!outers.isEmpty()) {
-                errors.add(new TestError(this, Severity.WARNING,
-                        tr("Building inside building"), BUILDING_INSIDE_BUILDING, p));
-            }
-        }
-        
-        primitivesToCheck.clear();
-        index.clear();
-
-        super.endTest();
-    }
-}
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 6611)
@@ -116,4 +116,8 @@
                 if (i instanceof Instruction.AssignmentInstruction) {
                     final Instruction.AssignmentInstruction ai = (Instruction.AssignmentInstruction) i;
+                    if (ai.isSetInstruction) {
+                        containsSetClassExpression = true;
+                        continue;
+                    }
                     final String val = ai.val instanceof Expression
                             ? (String) ((Expression) ai.val).evaluate(new Environment())
@@ -141,6 +145,4 @@
                     } else if ("assertNoMatch".equals(ai.key) && val != null) {
                         check.assertions.put(val, false);
-                    } else if (ai.val instanceof Boolean && ((Boolean) ai.val)) {
-                        containsSetClassExpression = true;
                     } else {
                         throw new RuntimeException("Cannot add instruction " + ai.key + ": " + ai.val + "!");
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 6611)
@@ -215,3 +215,7 @@
         index = null;
     }
+
+    public Cascade getCascade(String layer) {
+        return mc == null ? null : mc.getCascade(layer == null ? this.layer : layer);
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 6611)
@@ -291,5 +291,5 @@
         @Override
         public boolean applies(Environment env) {
-            return env != null && env.mc != null && env.mc.getCascade(env.layer) != null && not ^ env.mc.getCascade(env.layer).containsKey(id);
+            return env != null && env.getCascade(env.layer) != null && not ^ env.getCascade(env.layer).containsKey(id);
         }
 
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 6611)
@@ -5,4 +5,8 @@
 
 import java.awt.Color;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
@@ -30,4 +34,11 @@
  */
 public final class ExpressionFactory {
+
+    /**
+     * Marks functions which should be executed also when one or more arguments are null.
+     */
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.RUNTIME)
+    static @interface NullableArguments {}
 
     private static final List<Method> arrayFunctions;
@@ -146,4 +157,13 @@
 
         /**
+         * Returns the first non-null object. The name originates from the {@code COALESCE} SQL function.
+         * @see Utils#firstNonNull(Object[])
+         */
+        @NullableArguments
+        public static Object coalesce(Object... args) {
+            return Utils.firstNonNull(args);
+        }
+
+        /**
          * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
          * @since 5699
@@ -221,8 +241,9 @@
          * Assembles the strings to one.
          */
+        @NullableArguments
         public static String concat(Object... args) {
             StringBuilder res = new StringBuilder();
             for (Object f : args) {
-                res.append(f.toString());
+                res.append(String.valueOf(f));
             }
             return res.toString();
@@ -240,11 +261,5 @@
          */
         public Object prop(String key, String layer) {
-            Cascade c;
-            if (layer == null) {
-                c = env.mc.getCascade(env.layer);
-            } else {
-                c = env.mc.getCascade(layer);
-            }
-            return c.get(key);
+            return env.getCascade(layer).get(key);
         }
 
@@ -260,14 +275,5 @@
          */
         public Boolean is_prop_set(String key, String layer) {
-            Cascade c;
-            if (layer == null) {
-                // env.layer is null if expression is evaluated
-                // in ExpressionCondition, but MultiCascade.getCascade
-                // handles this
-                c = env.mc.getCascade(env.layer);
-            } else {
-                c = env.mc.getCascade(layer);
-            }
-            return c.containsKey(key);
+            return env.getCascade(layer).containsKey(key);
         }
 
@@ -650,5 +656,5 @@
             for (int i = 0; i < args.size(); ++i) {
                 convertedArgs[i] = Cascade.convertTo(args.get(i).evaluate(env), expectedParameterTypes[i]);
-                if (convertedArgs[i] == null) {
+                if (convertedArgs[i] == null && m.getAnnotation(NullableArguments.class) == null) {
                     return null;
                 }
@@ -696,5 +702,5 @@
             for (int i = 0; i < args.size(); ++i) {
                 Object o = Cascade.convertTo(args.get(i).evaluate(env), arrayComponentType);
-                if (o == null) {
+                if (o == null && m.getAnnotation(NullableArguments.class) == null) {
                     return null;
                 }
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Instruction.java	(revision 6611)
@@ -31,7 +31,9 @@
         public final String key;
         public final Object val;
+        public final boolean isSetInstruction;
 
-        public AssignmentInstruction(String key, Object val) {
+        public AssignmentInstruction(String key, Object val, boolean isSetInstruction) {
             this.key = key;
+            this.isSetInstruction = isSetInstruction;
             if (val instanceof LiteralExpression) {
                 Object litValue = ((LiteralExpression) val).evaluate(null);
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 6611)
@@ -472,5 +472,5 @@
             key=<IDENT> w()
             ( <EQUAL> val=expression() )?
-            { ins.add(new Instruction.AssignmentInstruction(key.image, val == null ? true : val)); }
+            { ins.add(new Instruction.AssignmentInstruction(key.image, val == null ? true : val, true)); }
             ( <RBRACE> { return ins; } | <SEMICOLON> w() )
         )
@@ -480,5 +480,5 @@
             LOOKAHEAD( float_array() w() ( <SEMICOLON> | <RBRACE> ) )
                 val=float_array()
-                { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
+                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
                 w()
                 ( <RBRACE> { return ins; } | <SEMICOLON> w() )
@@ -486,8 +486,8 @@
             LOOKAHEAD( expression() ( <SEMICOLON> | <RBRACE> ) )
                 val=expression()
-                { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
+                { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
                 ( <RBRACE> { return ins; } | <SEMICOLON> w() )
             |
-                val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
+                val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val, false)); }
         )
     )*
Index: /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java	(revision 6610)
+++ /trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java	(revision 6611)
@@ -160,8 +160,8 @@
             @Override
             public void visit(Node n) {
-                if (e.parent == null && right.matches(e.withPrimitive(n))) {
+                if (e.child == null && left.matches(e.withPrimitive(n))) {
                     if (e.osm instanceof Way && Geometry.nodeInsidePolygon(n, ((Way) e.osm).getNodes())
                             || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isNodeInsideMultiPolygon(n, (Relation) e.osm, null)) {
-                        e.parent = n;
+                        e.child = n;
                     }
                 }
@@ -170,8 +170,8 @@
             @Override
             public void visit(Way w) {
-                if (e.parent == null && right.matches(e.withPrimitive(w))) {
+                if (e.child == null && left.matches(e.withPrimitive(w))) {
                     if (e.osm instanceof Way && Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals(Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))
                             || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) e.osm, null)) {
-                        e.parent = w;
+                        e.child = w;
                     }
                 }
@@ -184,5 +184,5 @@
             public void visit(Collection<? extends OsmPrimitive> primitives) {
                 for (OsmPrimitive p : primitives) {
-                    if (e.parent != null) {
+                    if (e.child != null) {
                         // abort if first match has been found
                         break;
@@ -206,5 +206,5 @@
                     return false;
                 }
-                e.child = e.osm;
+                e.parent = e.osm;
 
                 final ContainsFinder containsFinder = new ContainsFinder(e);
@@ -221,5 +221,5 @@
                 }
 
-                return e.parent != null;
+                return e.child != null;
 
             } else if (ChildOrParentSelectorType.CHILD.equals(type)) {
@@ -374,8 +374,10 @@
 
         public boolean matchesBase(OsmPrimitiveType type) {
-            if (OsmPrimitiveType.NODE.equals(type)) {
-                return base.equals("node") || base.equals("*");
+            if (base.equals("*")) {
+                return true;
+            } else if (OsmPrimitiveType.NODE.equals(type)) {
+                return base.equals("node");
             } else if (OsmPrimitiveType.WAY.equals(type)) {
-                return base.equals("way") || base.equals("area") || base.equals("*");
+                return base.equals("way") || base.equals("area");
             } else if (OsmPrimitiveType.RELATION.equals(type)) {
                 return base.equals("area") || base.equals("relation") || base.equals("canvas");
