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<Entity> ssc = new SimpleSearchCondition<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<String,ConditionType> map;
+ * map.put("name", ConditionType.EQUALS);
+ * map.put("level", ConditionType.GREATER_THAN);
+ * ssc = new SimpleSearchCondition<Entity>(
+ * ConditionType.GREATER_THAN, template);
+ *
+ * ssc.isMet(new Entity("ccc", 30, "other message"));
+ * // false due to expression '"aaa"=="ccc" and 30>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;
+ }
+ }
+}