You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by am...@apache.org on 2010/03/03 18:00:36 UTC

svn commit: r918570 - in /cxf/trunk/rt/frontend/jaxrs/src: main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java test/java/org/apache/cxf/jaxrs/ext/search/ test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java

Author: amichalec
Date: Wed Mar  3 17:00:35 2010
New Revision: 918570

URL: http://svn.apache.org/viewvc?rev=918570&view=rev
Log:
atom search cntd

Added:
    cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/
    cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
Modified:
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java?rev=918570&r1=918569&r2=918570&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java Wed Mar  3 17:00:35 2010
@@ -18,22 +18,90 @@
  */
 package org.apache.cxf.jaxrs.ext.search;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class SimpleSearchCondition<T> implements SearchCondition<T> {
 
+    private static Set<ConditionType> supportedTypes = new HashSet<ConditionType>();
+    static {
+        supportedTypes.add(ConditionType.EQUALS);
+        supportedTypes.add(ConditionType.GREATER_THAN);
+        supportedTypes.add(ConditionType.GREATER_OR_EQUALS);
+        supportedTypes.add(ConditionType.LESS_THAN);
+        supportedTypes.add(ConditionType.LESS_OR_EQUALS);
+    }
     private ConditionType cType;
+    private Map<String, ConditionType> getters2operators;
     private T condition;
-    
+    private List<Method> getters;
+
+    /**
+     * Creates search condition with same operator (equality, inequality) applied in all comparison; see
+     * {@link #isMet(Object)} for details of comparison.
+     * 
+     * @param cType shared condition type
+     * @param condition template object
+     */
     public SimpleSearchCondition(ConditionType cType, T condition) {
+        if (cType == null) {
+            throw new IllegalArgumentException("cType is null");
+        }
+        if (condition == null) {
+            throw new IllegalArgumentException("condition is null");
+        }
+        if (!supportedTypes.contains(cType)) {
+            throw new IllegalArgumentException("unsupported condition type: " + cType.name());
+        }
         this.cType = cType;
+        this.getters2operators = null;
         this.condition = condition;
     }
-    
+
+    /**
+     * Creates search condition with different operators (equality, inequality etc) specified for each getter;
+     * see {@link #isMet(Object)} for details of comparison. Cannot be used for primitive T type due to
+     * per-getter comparison strategy.
+     * 
+     * @param getters2operators getters names and operators to be used with them during comparison
+     * @param condition template object
+     */
+    public SimpleSearchCondition(Map<String, ConditionType> getters2operators, T condition) {
+        if (getters2operators == null) {
+            throw new IllegalArgumentException("getters2operators is null");
+        }
+        if (condition == null) {
+            throw new IllegalArgumentException("condition is null");
+        }
+        if (isPrimitive(condition)) {
+            throw new IllegalArgumentException("mapped operators strategy is "
+                                               + "not supported for primitive type "
+                                               + condition.getClass().getName());
+        }
+        for (ConditionType ct : getters2operators.values()) {
+            if (!supportedTypes.contains(ct)) {
+                throw new IllegalArgumentException("unsupported condition type: " + ct.name());
+            }
+        }
+        this.cType = null;
+        this.getters2operators = getters2operators;
+        this.condition = condition;
+    }
+
     public T getCondition() {
         return condition;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * When constructor with map is used it returns null.
+     */
     public ConditionType getConditionType() {
         return cType;
     }
@@ -42,19 +110,161 @@
         return null;
     }
 
+    /**
+     * Compares given object against template condition object.
+     * <p>
+     * For primitive type T like String, Number (precisely, from type T located in subpackage of
+     * "java.lang.*") given object is directly compared with template object. Comparison for
+     * {@link ConditionType#EQUALS} requires correct implementation of {@link Object#equals(Object)}, using
+     * inequalities requires type T implementing {@link Comparable}.
+     * <p>
+     * For other types comparison of given object against template object is done using these <b>getters</b>;
+     * returned "is met" value is <b>conjunction (*and*)</b> of comparisons per each getter. Getters of
+     * template object that return null or throw exception are not used in comparison, in extreme if all
+     * getters are excluded it means every given pojo object matches. If
+     * {@link #SimpleSearchCondition(ConditionType, Object) constructor with shared operator} was used, then
+     * getters are compared using the same operator. If {@link #SimpleSearchCondition(Map, Object) constructor
+     * with map of operators} was used then for every getter specified operator is used (getters for missing
+     * mapping are ignored). The way that comparison per getter is done depends on operator type per getter -
+     * comparison for {@link ConditionType#EQUALS} requires correct implementation of
+     * {@link Object#equals(Object)}, using inequalities requires that getter type implements
+     * {@link Comparable}.
+     * <p>
+     * <b>Example:</b>
+     * 
+     * <pre>
+     * class Entity {
+     *   public String getName() {...
+     *   public int getLevel() {...
+     *   public String getMessage() {...
+     * }
+     * 
+     * Entity template = new Entity("bbb", 10, null);
+     * SimpleSearchCondition&lt;Entity> ssc = new SimpleSearchCondition&lt;Entity>(
+     *   ConditionType.GREATER_THAN, template);    
+     * 
+     * ssc.isMet(new Entity("aaa", 20, "some mesage")); 
+     * // false: is not met, expression '"aaa">"bbb" and 20>10' is not true  
+     * // since "aaa" is not greater than "bbb"; not that message is null in template hence ingored
+     * 
+     * ssc.isMet(new Entity("ccc", 30, "other message"));
+     * // true: is met, expression '"ccc">"bbb" and 30>10' is true
+     * 
+     * Map&lt;String,ConditionType> map;
+     * map.put("name", ConditionType.EQUALS);
+     * map.put("level", ConditionType.GREATER_THAN);
+     * ssc = new SimpleSearchCondition&lt;Entity&gt;(
+     *   ConditionType.GREATER_THAN, template);
+     *   
+     * ssc.isMet(new Entity("ccc", 30, "other message"));
+     * // false due to expression '"aaa"=="ccc" and 30&gt;10"' (note different operators)
+     * 
+     * </pre>
+     * 
+     * @throws IllegalAccessException when security manager disallows reflective call of getters.
+     */
     public boolean isMet(T pojo) {
-        // not implemented yet
-        
-        // we need to get all getters from the condition
-        // and then depending on ConditionType do equals, compare, etc against
-        // corresponding values returned from pojo getters
-        
-        return false;
+        if (isPrimitive(pojo)) {
+            return compare(pojo, cType, condition);
+        } else {
+            boolean matches = false;
+            for (Method getter : loadGetters()) {
+                ConditionType ct = cType;
+                if (ct == null) {
+                    ct = getters2operators.get(getterName(getter));
+                    if (ct == null) {
+                        continue;
+                    }
+                }
+                Object lval = getValue(getter, pojo);
+                Object rval = getValue(getter, condition);
+                matches = compare(lval, ct, rval);
+                if (!matches) {
+                    break;
+                }
+            }
+            return matches;
+        }
+    }
+
+    private List<Method> loadGetters() {
+        if (getters == null) {
+            getters = new ArrayList<Method>();
+            for (Method m : condition.getClass().getMethods()) {
+                if (isGetter(m)) {
+                    getters.add(m);
+                }
+            }
+        }
+        return getters;
+    }
+
+    private boolean isPrimitive(T pojo) {
+        return pojo.getClass().getName().startsWith("java.lang");
+    }
+
+    private boolean isGetter(Method m) {
+        return m.getParameterTypes().length == 0
+               && (m.getName().startsWith("get") || m.getName().startsWith("is"));
+    }
+    
+    private String getterName(Method m) {
+        return m.getName().replace("is", "").replace("get", "").toLowerCase();
+    }
+
+    private Object getValue(Method getter, T pojo) {
+        try {
+            return getter.invoke(pojo);
+        } catch (IllegalArgumentException e) {
+            throw e;
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (InvocationTargetException e) {
+            // getter exception is null equivalent
+            return null;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private boolean compare(Object lval, ConditionType cond, Object rval) {
+        boolean compares = true;
+        if (cond == ConditionType.EQUALS) {
+            compares = (lval != null) ? lval.equals(rval) : true;
+        } else {
+            if (lval instanceof Comparable && rval instanceof Comparable) {
+                Comparable lcomp = (Comparable)lval;
+                Comparable rcomp = (Comparable)rval;
+                int comp = lcomp.compareTo(rcomp);
+                switch (cond) {
+                case GREATER_THAN:
+                    compares = comp > 0;
+                    break;
+                case GREATER_OR_EQUALS:
+                    compares = comp >= 0;
+                    break;
+                case LESS_THAN:
+                    compares = comp < 0;
+                    break;
+                case LESS_OR_EQUALS:
+                    compares = comp <= 0;
+                    break;
+                default:
+                    String msg = String.format("Condition type %s is not supported", cond.name());
+                    throw new RuntimeException(msg);
+                }
+            }
+        }
+        return compares;
     }
 
     public List<T> findAll(List<T> pojos) {
-        // TODO Auto-generated method stub
-        return null;
+        List<T> result = new ArrayList<T>();
+        for (T pojo : pojos) {
+            if (isMet(pojo)) {
+                result.add(pojo);
+            }
+        }
+        return result;
     }
 
 }

Added: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java?rev=918570&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java Wed Mar  3 17:00:35 2010
@@ -0,0 +1,335 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.jaxrs.ext.search;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class SimpleSearchConditionTest {
+
+    private static SearchCondition<SingleAttr> cEq;
+    private static SearchCondition<SingleAttr> cGt;
+    private static SearchCondition<SingleAttr> cGeq;
+    private static SearchCondition<SingleAttr> cLt;
+    private static SearchCondition<SingleAttr> cLeq;
+
+    private static SingleAttr attr = new SingleAttr("bbb");
+    private static SingleAttr attrGreater = new SingleAttr("ccc");
+    private static SingleAttr attrLesser = new SingleAttr("aaa");
+
+    // TODO 1. comparison with multiple values
+    // TODO 2. comparison when getter returns null/throws exception
+    private static DoubleAttr attr2Vals = new DoubleAttr("bbb", "ccc");
+    private static DoubleAttr attr2ValsGreater = new DoubleAttr("ccc", "ddd");
+    private static DoubleAttr attr2ValsLesser = new DoubleAttr("aaa", "bbb");
+
+    private static DoubleAttr attr1Val = new DoubleAttr("bbb", null);
+    private static DoubleAttr attr1ValGreater = new DoubleAttr("ccc", "ingored");
+    private static DoubleAttr attr1ValLesser = new DoubleAttr("aaa", "ingored");
+
+    private static SearchCondition<DoubleAttr> dc1Eq;
+    private static SearchCondition<DoubleAttr> dc1Gt;
+    private static SearchCondition<DoubleAttr> dc1Geq;
+    private static SearchCondition<DoubleAttr> dc1Lt;
+    private static SearchCondition<DoubleAttr> dc1Leq;
+
+    private static SearchCondition<DoubleAttr> dc2Eq;
+    private static SearchCondition<DoubleAttr> dc2Gt;
+    private static SearchCondition<DoubleAttr> dc2Geq;
+    private static SearchCondition<DoubleAttr> dc2Lt;
+    private static SearchCondition<DoubleAttr> dc2Leq;
+
+    private static List<ConditionType> supported = Arrays.asList(ConditionType.EQUALS,
+                                                                 ConditionType.GREATER_OR_EQUALS,
+                                                                 ConditionType.GREATER_THAN,
+                                                                 ConditionType.LESS_OR_EQUALS,
+                                                                 ConditionType.LESS_THAN);
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        cEq = new SimpleSearchCondition<SingleAttr>(ConditionType.EQUALS, attr);
+        cGt = new SimpleSearchCondition<SingleAttr>(ConditionType.GREATER_THAN, attr);
+        cGeq = new SimpleSearchCondition<SingleAttr>(ConditionType.GREATER_OR_EQUALS, attr);
+        cLt = new SimpleSearchCondition<SingleAttr>(ConditionType.LESS_THAN, attr);
+        cLeq = new SimpleSearchCondition<SingleAttr>(ConditionType.LESS_OR_EQUALS, attr);
+
+        dc1Eq = new SimpleSearchCondition<DoubleAttr>(ConditionType.EQUALS, attr1Val);
+        dc1Gt = new SimpleSearchCondition<DoubleAttr>(ConditionType.GREATER_THAN, attr1Val);
+        dc1Geq = new SimpleSearchCondition<DoubleAttr>(ConditionType.GREATER_OR_EQUALS, attr1Val);
+        dc1Lt = new SimpleSearchCondition<DoubleAttr>(ConditionType.LESS_THAN, attr1Val);
+        dc1Leq = new SimpleSearchCondition<DoubleAttr>(ConditionType.LESS_OR_EQUALS, attr1Val);
+
+        dc2Eq = new SimpleSearchCondition<DoubleAttr>(ConditionType.EQUALS, attr2Vals);
+        dc2Gt = new SimpleSearchCondition<DoubleAttr>(ConditionType.GREATER_THAN, attr2Vals);
+        dc2Geq = new SimpleSearchCondition<DoubleAttr>(ConditionType.GREATER_OR_EQUALS, attr2Vals);
+        dc2Lt = new SimpleSearchCondition<DoubleAttr>(ConditionType.LESS_THAN, attr2Vals);
+        dc2Leq = new SimpleSearchCondition<DoubleAttr>(ConditionType.LESS_OR_EQUALS, attr2Vals);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCtorNull1() {
+        new SimpleSearchCondition<SingleAttr>((ConditionType)null, attr);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCtorNull2() {
+        new SimpleSearchCondition<SingleAttr>(ConditionType.LESS_THAN, null);
+    }
+
+    @Test
+    public void testCtorCondSupported() {
+        for (ConditionType ct : ConditionType.values()) {
+            try {
+                new SimpleSearchCondition<SingleAttr>(ct, attr);
+                if (!supported.contains(ct)) {
+                    fail(String.format("Not supported type %s should throw exception", ct.name()));
+                }
+            } catch (IllegalArgumentException e) {
+                if (supported.contains(ct)) {
+                    fail(String.format("Supported type %s should not throw exception", ct.name()));
+                }
+            }
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCtorMapNull() {
+        new SimpleSearchCondition<SingleAttr>((Map<String, ConditionType>)null, attr);
+    }
+    
+    @Test
+    public void testCtorMapCondSupported() {
+        for (ConditionType ct : ConditionType.values()) {
+            try {
+                Map<String, ConditionType> map = new HashMap<String, ConditionType>();
+                map.put("foo", ct);
+                new SimpleSearchCondition<SingleAttr>(map, attr);
+                if (!supported.contains(ct)) {
+                    fail(String.format("Not supported type %s should throw exception", ct.name()));
+                }
+            } catch (IllegalArgumentException e) {
+                if (supported.contains(ct)) {
+                    fail(String.format("Supported type %s should not throw exception", ct.name()));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testGetCondition() {
+        assertEquals(cLeq.getCondition(), attr);
+    }
+
+    @Test
+    public void testGetConditionType() {
+        assertEquals(cEq.getConditionType(), ConditionType.EQUALS);
+        assertEquals(cLt.getConditionType(), ConditionType.LESS_THAN);
+    }
+
+    @Test
+    public void testGetConditions() {
+        assertEquals(cGt.getConditions(), null);
+    }
+
+    @Test
+    public void testIsMetEq() {
+        assertTrue(cEq.isMet(attr));
+        assertFalse(cEq.isMet(attrGreater));
+    }
+
+    @Test
+    public void testIsMetGt() {
+        assertTrue(cGt.isMet(attrGreater));
+        assertFalse(cGt.isMet(attr));
+        assertFalse(cGt.isMet(attrLesser));
+    }
+
+    @Test
+    public void testIsMetGeq() {
+        assertTrue(cGeq.isMet(attrGreater));
+        assertTrue(cGeq.isMet(attr));
+        assertFalse(cGeq.isMet(attrLesser));
+    }
+
+    @Test
+    public void testIsMetLt() {
+        assertFalse(cLt.isMet(attrGreater));
+        assertFalse(cLt.isMet(attr));
+        assertTrue(cLt.isMet(attrLesser));
+    }
+
+    @Test
+    public void testIsMetLeq() {
+        assertFalse(cLeq.isMet(attrGreater));
+        assertTrue(cLeq.isMet(attr));
+        assertTrue(cLeq.isMet(attrLesser));
+    }
+
+    @Test
+    public void testIsMetEqPrimitive() {
+        assertTrue(new SimpleSearchCondition<String>(ConditionType.EQUALS, "foo").isMet("foo"));
+    }
+
+    @Test
+    public void testIsMetGtPrimitive() {
+        assertTrue(new SimpleSearchCondition<Float>(ConditionType.GREATER_THAN, 1.5f).isMet(2.5f));
+    }
+
+    @Test
+    public void testIsMetLtPrimitive() {
+        assertTrue(new SimpleSearchCondition<Integer>(ConditionType.LESS_THAN, 10).isMet(5));
+    }
+
+    @Test
+    public void testFindAll() {
+        List<SingleAttr> inputs = Arrays.asList(attr, attrGreater, attrLesser);
+        List<SingleAttr> found = Arrays.asList(attr, attrGreater);
+        assertEquals(found, cGeq.findAll(inputs));
+    }
+
+    @Test
+    public void testIsMetEqDouble1Val() {
+        assertFalse(dc1Eq.isMet(attr1ValGreater));
+        assertTrue(dc1Eq.isMet(attr1Val));
+        assertFalse(dc1Eq.isMet(attr1ValLesser));
+    }
+
+    @Test
+    public void testIsMetGtDouble1Val() {
+        assertTrue(dc1Gt.isMet(attr1ValGreater));
+        assertFalse(dc1Gt.isMet(attr1Val));
+        assertFalse(dc1Gt.isMet(attr1ValLesser));
+    }
+
+    @Test
+    public void testIsMetGeqDouble1Val() {
+        assertTrue(dc1Geq.isMet(attr1ValGreater));
+        assertTrue(dc1Geq.isMet(attr1Val));
+        assertFalse(dc1Geq.isMet(attr1ValLesser));
+    }
+
+    @Test
+    public void testIsMetLtDouble1Val() {
+        assertFalse(dc1Lt.isMet(attr1ValGreater));
+        assertFalse(dc1Lt.isMet(attr1Val));
+        assertTrue(dc1Lt.isMet(attr1ValLesser));
+    }
+
+    @Test
+    public void testIsMetLeqDouble1Val() {
+        assertFalse(dc1Leq.isMet(attr1ValGreater));
+        assertTrue(dc1Leq.isMet(attr1Val));
+        assertTrue(dc1Leq.isMet(attr1ValLesser));
+    }
+
+    @Test
+    public void testIsMetEqDouble2Vals() {
+        assertFalse(dc2Eq.isMet(attr2ValsGreater));
+        assertTrue(dc2Eq.isMet(attr2Vals));
+        assertFalse(dc2Eq.isMet(attr2ValsLesser));
+    }
+
+    @Test
+    public void testIsMetGtDouble2Vals() {
+        assertTrue(dc2Gt.isMet(attr2ValsGreater));
+        assertFalse(dc2Gt.isMet(attr2Vals));
+        assertFalse(dc2Gt.isMet(attr2ValsLesser));
+    }
+
+    @Test
+    public void testIsMetGeqDouble2Vals() {
+        assertTrue(dc2Geq.isMet(attr2ValsGreater));
+        assertTrue(dc2Geq.isMet(attr2Vals));
+        assertFalse(dc2Geq.isMet(attr2ValsLesser));
+    }
+
+    @Test
+    public void testIsMetLtDouble2Vals() {
+        assertFalse(dc2Lt.isMet(attr2ValsGreater));
+        assertFalse(dc2Lt.isMet(attr2Vals));
+        assertTrue(dc2Lt.isMet(attr2ValsLesser));
+    }
+
+    @Test
+    public void testIsMetLeqDouble2Vals() {
+        assertFalse(dc2Leq.isMet(attr2ValsGreater));
+        assertTrue(dc2Leq.isMet(attr2Vals));
+        assertTrue(dc2Leq.isMet(attr2ValsLesser));
+    }
+
+    @Test
+    public void testIsMetMappedOperators() {
+        Map<String, ConditionType> map = new HashMap<String, ConditionType>();
+        map.put("foo", ConditionType.LESS_THAN);
+        map.put("bar", ConditionType.GREATER_THAN);
+        
+        // expression "template.getFoo() < pojo.getFoo() & template.getBar() > pojo.getBar()"
+        assertTrue(new SimpleSearchCondition<DoubleAttr>(map, new DoubleAttr("bbb", "ccc"))
+            .isMet(new DoubleAttr("aaa", "ddd")));
+        
+        // expression "template.getBar() > pojo.getBar()"
+        assertTrue(new SimpleSearchCondition<DoubleAttr>(map, new DoubleAttr(null, "ccc"))
+            .isMet(new DoubleAttr("!not-interpreted!", "ddd")));
+    }
+
+    static class SingleAttr {
+        private String foo;
+
+        public SingleAttr(String foo) {
+            this.foo = foo;
+        }
+
+        public String getFoo() {
+            return foo;
+        }
+
+        // this should not be used by "isMet" (is not public)
+        @SuppressWarnings("unused")
+        private String getBar() {
+            return "it's private!";
+        }
+    }
+
+    static class DoubleAttr {
+        private String foo;
+        private String bar;
+
+        public DoubleAttr(String foo, String bar) {
+            this.foo = foo;
+            this.bar = bar;
+        }
+
+        public String getFoo() {
+            return foo;
+        }
+
+        public String getBar() {
+            return bar;
+        }
+    }
+}