Commits

Anonymous committed 37d5439

Add ability to log or throw exceptions for expressions that contain non-existant properties or invalid methods

Issue Number: XW-557
Submitted by: Matt Raible

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@1647e221344d-f017-0410-9bd5-d282ab1896d7

Comments (0)

Files changed (3)

src/java/com/opensymphony/xwork2/ognl/OgnlUtil.java

 public class OgnlUtil {
 
     private static final Logger LOG = LoggerFactory.getLogger(OgnlUtil.class);
-    private ConcurrentHashMap<String,Object> expressions = new ConcurrentHashMap<String,Object>();
-    private ConcurrentHashMap<Class,BeanInfo> beanInfoCache = new ConcurrentHashMap<Class,BeanInfo>();
-    
+    private ConcurrentHashMap<String, Object> expressions = new ConcurrentHashMap<String, Object>();
+    private ConcurrentHashMap<Class, BeanInfo> beanInfoCache = new ConcurrentHashMap<Class, BeanInfo>();
+
     private TypeConverter defaultConverter;
-    
+
     @Inject
     public void setXWorkConverter(XWorkConverter conv) {
         this.defaultConverter = new OgnlTypeConverterWrapper(conv);
     }
-    
+
     /**
      * Sets the object's properties using the default type converter, defaulting to not throw
      * exceptions for problems setting the properties.
 
     /**
      * Get's the java beans property descriptors for the given source.
-     * 
-     * @param source  the source object.
-     * @return  property descriptors.
+     *
+     * @param source the source object.
+     * @return property descriptors.
      * @throws IntrospectionException is thrown if an exception occurs during introspection.
      */
     public PropertyDescriptor[] getPropertyDescriptors(Object source) throws IntrospectionException {
         return beanInfo.getPropertyDescriptors();
     }
 
+
+    /**
+     * Get's the java beans property descriptors for the given class.
+     *
+     * @param clazz the source object.
+     * @return property descriptors.
+     * @throws IntrospectionException is thrown if an exception occurs during introspection.
+     */
+    public PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws IntrospectionException {
+        BeanInfo beanInfo = getBeanInfo(clazz);
+        return beanInfo.getPropertyDescriptors();
+    }
+
     /**
      * Creates a Map with read properties for the given source object.
      * <p/>
      * If the source object does not have a read property (i.e. write-only) then
      * the property is added to the map with the value <code>here is no read method for property-name</code>.
-     * 
-     * @param source   the source object.
-     * @return  a Map with (key = read property name, value = value of read property).
+     *
+     * @param source the source object.
+     * @return a Map with (key = read property name, value = value of read property).
      * @throws IntrospectionException is thrown if an exception occurs during introspection.
-     * @throws OgnlException is thrown by OGNL if the property value could not be retrieved
+     * @throws OgnlException          is thrown by OGNL if the property value could not be retrieved
      */
     public Map getBeanMap(Object source) throws IntrospectionException, OgnlException {
         Map beanMap = new HashMap();
     }
 
     /**
-     * Get's the java bean info for the given source.
-     * 
-     * @param from  the source object.
-     * @return  java bean info.
+     * Get's the java bean info for the given source object. Calls getBeanInfo(Class c).
+     *
+     * @param from the source object.
+     * @return java bean info.
      * @throws IntrospectionException is thrown if an exception occurs during introspection.
      */
     public BeanInfo getBeanInfo(Object from) throws IntrospectionException {
-        BeanInfo beanInfo;
-        beanInfo = (BeanInfo) beanInfoCache.get(from.getClass());
-        if (beanInfo == null) {
-            beanInfo = Introspector.getBeanInfo(from.getClass(), Object.class);
-            beanInfoCache.putIfAbsent(from.getClass(), beanInfo);
+        return getBeanInfo(from.getClass());
+    }
+
+
+    /**
+     * Get's the java bean info for the given source.
+     *
+     * @param clazz the source class.
+     * @return java bean info.
+     * @throws IntrospectionException is thrown if an exception occurs during introspection.
+     */
+    public BeanInfo getBeanInfo(Class clazz) throws IntrospectionException {
+        synchronized (beanInfoCache) {
+            BeanInfo beanInfo;
+            beanInfo = beanInfoCache.get(clazz);
+            if (beanInfo == null) {
+                beanInfo = Introspector.getBeanInfo(clazz, Object.class);
+                beanInfoCache.put(clazz, beanInfo);
+            }
+            return beanInfo;
         }
-        return beanInfo;
     }
 
     void internalSetProperty(String name, Object value, Object o, Map context, boolean throwPropertyExceptions) {
             }
         }
     }
-    
+
     TypeConverter getTypeConverterFromContext(Map context) {
         /*ValueStack stack = (ValueStack) context.get(ActionContext.VALUE_STACK);
         Container cont = (Container)stack.getContext().get(ActionContext.CONTAINER);

src/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java

 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
 
 import ognl.Ognl;
 import ognl.OgnlContext;
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.TextProvider;
 import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.ActionSupport;
 import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
 import com.opensymphony.xwork2.inject.Container;
 import com.opensymphony.xwork2.inject.Inject;
  *
  * @author Patrick Lightbody
  * @author tm_jee
- * 
  * @version $Date$ $Id$
  */
 public class OgnlValueStack implements Serializable, ValueStack {
-	
-	private static final long serialVersionUID = 370737852934925530L;
-	
+
+    private static final long serialVersionUID = 370737852934925530L;
+
     private static Logger LOG = LoggerFactory.getLogger(OgnlValueStack.class);
     private boolean devMode;
 
     Class defaultType;
     Map overrides;
     transient OgnlUtil ognlUtil;
-    
+
     protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
         setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
         push(prov);
     protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) {
         setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess);
     }
-    
+
     @Inject
     public void setOgnlUtil(OgnlUtil ognlUtil) {
         this.ognlUtil = ognlUtil;
     }
-    
+
     protected void setRoot(XWorkConverter xworkConverter,
-            CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) {
+                           CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) {
         this.root = compoundRoot;
         this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter),
                 new StaticMemberAccess(allowStaticMethodAccess));
         ((OgnlContext) context).setTraceEvaluations(false);
         ((OgnlContext) context).setKeepLastEvaluation(false);
     }
-    
+
     @Inject("devMode")
     public void setDevMode(String mode) {
         devMode = "true".equalsIgnoreCase(mode);
      * @see com.opensymphony.xwork2.util.ValueStack#setExprOverrides(java.util.Map)
      */
     public void setExprOverrides(Map overrides) {
-    	if (this.overrides == null) {
-    		this.overrides = overrides;
-    	}
-    	else {
-    		this.overrides.putAll(overrides);
-    	}
+        if (this.overrides == null) {
+            this.overrides = overrides;
+        } else {
+            this.overrides.putAll(overrides);
+        }
     }
-    
+
     /* (non-Javadoc)
-     * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides()
-     */
+    * @see com.opensymphony.xwork2.util.ValueStack#getExprOverrides()
+    */
     public Map getExprOverrides() {
-    	return this.overrides;
+        return this.overrides;
     }
 
     /* (non-Javadoc)
         } catch (OgnlException e) {
             if (throwExceptionOnFailure) {
                 e.printStackTrace(System.out);
-                System.out.println("expr: "+expr+" val: "+value+" context: "+context+" root:"+root+" value: "+value);
+                System.out.println("expr: " + expr + " val: " + value + " context: " + context + " root:" + root + " value: " + value);
                 String msg = "Error setting expression '" + expr + "' with value '" + value + "'";
                 throw new XWorkException(msg, e);
             } else {
             if (value != null) {
                 return value;
             } else {
+                checkForInvalidProperties(expr);
                 return findInContext(expr);
             }
         } catch (OgnlException e) {
+            checkForInvalidProperties(expr);
             return findInContext(expr);
         } catch (Exception e) {
             logLookupFailure(expr, e);
         return getContext().get(name);
     }
 
+
+     /**
+     * This method looks for matching methods/properties in an action to warn the user if
+     * they specified a property that doesn't exist.
+     * @param expr the property expression
+     */
+    private void checkForInvalidProperties(String expr) {
+        if (expr.contains("(") && expr.contains(")")) {
+            LOG.warn("Could not find method [" + expr + "]");
+        } else if (findInContext(expr) == null) {
+            // find objects with Action in them and inspect matching getters
+            Set availableProperties = new LinkedHashSet();
+            for (Object o : root) {
+                if (o instanceof ActionSupport || o.getClass().getSimpleName().endsWith("Action")) {
+                    try {
+                        findAvailableProperties(o.getClass(), expr, availableProperties, null);
+                    } catch (IntrospectionException ise) {
+                        // ignore
+                    }
+                }
+            }
+            if (!availableProperties.contains(expr)) {
+                LOG.warn("Could not find property [" + expr + "]");
+            }
+        }
+    }
+
+    /**
+     * Look for available properties on an existing class.
+     * @param c the class to search on
+     * @param expr the property expression
+     * @param availableProperties a set of properties found
+     * @param parent a parent property
+     * @throws IntrospectionException when Ognl can't get property descriptors
+     */
+    private void findAvailableProperties(Class c, String expr, Set availableProperties, String parent) throws IntrospectionException {
+        PropertyDescriptor[] descriptors = ognlUtil.getPropertyDescriptors(c);
+        for (PropertyDescriptor pd : descriptors) {
+            String name = pd.getDisplayName();
+            if (parent != null && expr.indexOf(".") > -1) {
+                name = expr.substring(0, expr.indexOf(".") + 1) + name;
+            }
+            if (expr.startsWith(name)) {
+               availableProperties.add((parent != null) ? parent + "." + name : name);
+                if (expr.equals(name)) break; // no need to go any further
+                if (expr.indexOf(".") > -1) {
+                    String property = expr.substring(expr.indexOf(".") + 1);
+                    // if there is a nested property (indicated by a dot), chop it off so we can look for method name
+                    String rawProperty = (property.indexOf(".") > -1) ? property.substring(0, property.indexOf(".")) : property;
+                    String methodToLookFor = "get" + rawProperty.substring(0, 1).toUpperCase() + rawProperty.substring(1);
+                    Method[] methods = pd.getPropertyType().getDeclaredMethods();
+                    for (Method method : methods) {
+                        if (method.getName().equals(methodToLookFor)) {
+                            availableProperties.add(name + "." + rawProperty);
+                            Class returnType = method.getReturnType();
+                            findAvailableProperties(returnType, property, availableProperties, name);
+                        }
+                    }
+
+                }
+            }
+        }
+    }
+
     /**
      * Log a failed lookup, being more verbose when devMode=true.
      *
     public void push(Object o) {
         root.push(o);
     }
+
     /* (non-Javadoc)
-     * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object)
-     */
+    * @see com.opensymphony.xwork2.util.ValueStack#set(java.lang.String, java.lang.Object)
+    */
     public void set(String key, Object o) {
-    	//set basically is backed by a Map
-    	//pushed on the stack with a key 
-    	//being put on the map and the 
-    	//Object being the value
-    	
-    	Map setMap=null;
-    	
-    	//check if this is a Map 
-    	//put on the stack  for setting
-    	//if so just use the old map (reduces waste)
-    	Object topObj=peek();
-    	if (topObj instanceof Map 
-    			&&((Map)topObj).get(MAP_IDENTIFIER_KEY)!=null) {
-    		
-    		setMap=(Map)topObj;
-    	}	else {
-    		setMap=new HashMap();
-    		//the map identifier key ensures
-    		//that this map was put there
-    		//for set purposes and not by a user
-    		//whose data we don't want to touch
-    		setMap.put(MAP_IDENTIFIER_KEY,"");
-    		push(setMap);
-    	}
-    	setMap.put(key,o);
-    	
+        //set basically is backed by a Map
+        //pushed on the stack with a key
+        //being put on the map and the
+        //Object being the value
+
+        Map setMap = null;
+
+        //check if this is a Map
+        //put on the stack  for setting
+        //if so just use the old map (reduces waste)
+        Object topObj = peek();
+        if (topObj instanceof Map
+                && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null) {
+
+            setMap = (Map) topObj;
+        } else {
+            setMap = new HashMap();
+            //the map identifier key ensures
+            //that this map was put there
+            //for set purposes and not by a user
+            //whose data we don't want to touch
+            setMap.put(MAP_IDENTIFIER_KEY, "");
+            push(setMap);
+        }
+        setMap.put(key, o);
+
     }
-    
-    
-    private static final String MAP_IDENTIFIER_KEY="com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY";
-    
+
+
+    private static final String MAP_IDENTIFIER_KEY = "com.opensymphony.xwork2.util.OgnlValueStack.MAP_IDENTIFIER_KEY";
+
     /* (non-Javadoc)
-     * @see com.opensymphony.xwork2.util.ValueStack#size()
-     */
+    * @see com.opensymphony.xwork2.util.ValueStack#size()
+    */
     public int size() {
         return root.size();
     }
-    
+
     private Object readResolve() {
         // TODO: this should be done better
         ActionContext ac = ActionContext.getContext();
         return aStack;
     }
 
- 
+
 }

src/test/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java

     }
 
     private OgnlUtil ognlUtil;
-    
+
     public void setUp() throws Exception {
         super.setUp();
         ognlUtil = container.getInstance(OgnlUtil.class);
     }
-    
+
     private OgnlValueStack createValueStack() {
         return createValueStack(true);
     }
+
     private OgnlValueStack createValueStack(boolean allowStaticMethodAccess) {
         OgnlValueStack stack = new OgnlValueStack(
                 container.getInstance(XWorkConverter.class),
-                (CompoundRootAccessor)container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
+                (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
                 container.getInstance(TextProvider.class, "system"), allowStaticMethodAccess);
         container.inject(stack);
         return stack;
     }
-    
+
     public void testExpOverridesCanStackExpUp() throws Exception {
-    	Map expr1 = new LinkedHashMap();
-    	expr1.put("expr1", "'expr1value'");
-    	
-    	OgnlValueStack vs = createValueStack();
-    	vs.setExprOverrides(expr1);
-    	
-    	assertEquals(vs.findValue("expr1"), "expr1value");
-    	
-    	Map expr2 = new LinkedHashMap();
-    	expr2.put("expr2", "'expr2value'");
-    	expr2.put("expr3", "'expr3value'");
-    	vs.setExprOverrides(expr2);
-    	
-    	assertEquals(vs.findValue("expr2"), "expr2value");
-    	assertEquals(vs.findValue("expr3"), "expr3value");
-    }
-    
+        Map expr1 = new LinkedHashMap();
+        expr1.put("expr1", "'expr1value'");
+
+        OgnlValueStack vs = createValueStack();
+        vs.setExprOverrides(expr1);
+
+        assertEquals(vs.findValue("expr1"), "expr1value");
+
+        Map expr2 = new LinkedHashMap();
+        expr2.put("expr2", "'expr2value'");
+        expr2.put("expr3", "'expr3value'");
+        vs.setExprOverrides(expr2);
+
+        assertEquals(vs.findValue("expr2"), "expr2value");
+        assertEquals(vs.findValue("expr3"), "expr3value");
+    }
+
 
     public void testArrayAsString() {
         OgnlValueStack vs = createValueStack();
         vs.push(dog);
         assertEquals("Rover", vs.findValue("name", String.class));
     }
-    
+
     public void testStatic() {
         OgnlValueStack vs = createValueStack();
 
         vs.push(dog);
         assertEquals("fido", vs.findValue("@com.opensymphony.xwork2.util.Dog@getDeity()", String.class));
     }
-    
+
     public void testStaticMethodDisallow() {
         OgnlValueStack vs = createValueStack(false);
 
         vs.push(dog);
         assertNull(vs.findValue("@com.opensymphony.xwork2.util.Dog@getDeity()", String.class));
     }
-    
+
     public void testBasicSet() {
-    	OgnlValueStack vs = createValueStack();
-        
-    	Dog dog = new Dog();
+        OgnlValueStack vs = createValueStack();
+
+        Dog dog = new Dog();
         dog.setAge(12);
         dog.setName("Rover");
 
-        vs.set("dog",dog);
+        vs.set("dog", dog);
         assertEquals("Rover", vs.findValue("dog.name", String.class));
     }
 
         action.setThrowException(false);
         assertEquals("OK", stack.findValue("exceptionMethod()"));
     }
-    
-    
+
 
     public void testCallMethodWithNullArg() {
         SimpleAction action = new SimpleAction();
         stack.getContext().put(XWorkConverter.REPORT_CONVERSION_ERRORS, Boolean.TRUE);
         stack.setDevMode("true");
         stack.push(action);
-        
+
         try {
             stack.setValue("bar", "3x");
             fail("Attempt to set 'bar' int property to '3x' should result in RuntimeException");
         }
-        catch(RuntimeException re) {
+        catch (RuntimeException re) {
             assertTrue(true);
         }
 
         Map conversionErrors = (Map) stack.getContext().get(ActionContext.CONVERSION_ERRORS);
         assertTrue(conversionErrors.containsKey("bar"));
     }
-    
+
 
     public void testObjectSettingWithInvalidValueDoesNotCauseSetCalledWithNull() {
         SimpleAction action = new SimpleAction();
         OgnlValueStack newVs = (OgnlValueStack) ois.readObject();
         assertEquals("Rover", newVs.findValue("name", String.class));
     }
-    
+
     public void testSetAfterPush() {
-    	OgnlValueStack vs = createValueStack();
+        OgnlValueStack vs = createValueStack();
+
+        Dog d = new Dog();
+        d.setName("Rover");
+        vs.push(d);
 
-    	Dog d=new Dog();
-    	d.setName("Rover");
-    	vs.push(d);
-    	
-    	vs.set("name","Bill");
+        vs.set("name", "Bill");
 
-    	assertEquals("Bill", vs.findValue("name"));
-    	
-    }    
+        assertEquals("Bill", vs.findValue("name"));
+
+    }
 
     public void testSetBarAsString() {
         Foo foo = new Foo();
     }
 
     public void testSetBeforePush() {
-    	OgnlValueStack vs = createValueStack();
-    	
-    	vs.set("name","Bill");
-    	Dog d=new Dog();
-    	d.setName("Rover");
-    	vs.push(d);
-    	
-    	assertEquals("Rover", vs.findValue("name"));
-    	
-    }
-    
+        OgnlValueStack vs = createValueStack();
+
+        vs.set("name", "Bill");
+        Dog d = new Dog();
+        d.setName("Rover");
+        vs.push(d);
+
+        assertEquals("Rover", vs.findValue("name"));
+
+    }
+
     public void testSetDeepBarAsString() {
         Foo foo = new Foo();
         Foo foo2 = new Foo();
         assertNotNull(((Cat) foo.getCats().get(0)).getFoo().getCats().get(1));
         assertEquals("Deep null cat", ((Cat) ((Cat) foo.getCats().get(0)).getFoo().getCats().get(1)).getName());
     }
-    
+
     public void testSetMultiple() {
-    	OgnlValueStack vs = createValueStack();
-    	int origSize=vs.getRoot().size();
-    	vs.set("something",new Object());
-    	vs.set("somethingElse",new Object());
-    	vs.set("yetSomethingElse",new Object());
-    	assertEquals(origSize+1,vs.getRoot().size());
-    	
-    }
-    
+        OgnlValueStack vs = createValueStack();
+        int origSize = vs.getRoot().size();
+        vs.set("something", new Object());
+        vs.set("somethingElse", new Object());
+        vs.set("yetSomethingElse", new Object());
+        assertEquals(origSize + 1, vs.getRoot().size());
+
+    }
+
     public void testSetNullMap() {
         Foo foo = new Foo();
         OgnlValueStack vs = createValueStack();
     public void testConstructorWithAStack() {
         OgnlValueStack stack = createValueStack();
         stack.push("Hello World");
-        
+
         OgnlValueStack stack2 = new OgnlValueStack(stack,
                 container.getInstance(XWorkConverter.class),
-                (CompoundRootAccessor)container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), true);
+                (CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), true);
         container.inject(stack2);
 
         assertEquals(stack.getRoot(), stack2.getRoot());
         assertEquals(stack.peek(), stack2.peek());
         assertEquals("Hello World", stack2.pop());
-        
+
     }
 
     public void testDefaultType() {
         OgnlValueStack stack = createValueStack();
         stack.setDefaultType(String.class);
         stack.push("Hello World");
-        
+
         assertEquals("Hello World", stack.findValue("top"));
         assertEquals(null, stack.findValue(null));
 
         stack.push(new Integer(123));
         assertEquals(new Integer(123), stack.findValue("top"));
     }
-    
+
     public void testFindString() {
         OgnlValueStack stack = createValueStack();
         stack.setDefaultType(Integer.class);
         stack.push("Hello World");
-    	
+
         assertEquals("Hello World", stack.findString("top"));
         assertEquals(null, stack.findString(null));
     }
 
     public void testExpOverrides() {
-    	Map overrides = new HashMap();
-    	overrides.put("claus", "top");
-    	
+        Map overrides = new HashMap();
+        overrides.put("claus", "top");
+
         OgnlValueStack stack = createValueStack();
         stack.setExprOverrides(overrides);
         stack.push("Hello World");
-        
+
         assertEquals("Hello World", stack.findValue("claus"));
         assertEquals("Hello World", stack.findString("claus"));
         assertEquals("Hello World", stack.findValue("top"));
 
         assertEquals("Hello World", stack.findValue("claus", String.class));
         assertEquals("Hello World", stack.findValue("top", String.class));
-        
+
         stack.getContext().put("santa", "Hello Santa");
         assertEquals("Hello Santa", stack.findValue("santa", String.class));
         assertEquals(null, stack.findValue("unknown", String.class));
     }
 
+    public void testWarnAboutInvalidProperties() {
+        OgnlValueStack stack = createValueStack();
+        MyAction action = new MyAction();
+        action.setName("Don");
+        stack.push(action);
+
+        // how to test the warning was logged?
+        assertEquals("Don", stack.findValue("name", String.class));
+        assertEquals(null, stack.findValue("address", String.class));
+        // should log warning
+        assertEquals(null, stack.findValue("address.invalidProperty", String.class));
+
+        // if country is null, OGNL throws an exception
+        /*action.setAddress(new Address());
+        stack.push(action);*/
+        // should log warning
+        assertEquals(null, stack.findValue("address.country.id", String.class));
+        assertEquals(null, stack.findValue("address.country.name", String.class));
+    }
+
     class BadJavaBean {
         private int count;
         private int count2;
             return count2;
         }
     }
+
+    class MyAction {
+        private Long id;
+        private String name;
+        private Address address;
+
+        public Long getId() {
+            return id;
+        }
+
+        public void setId(Long id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public Address getAddress() {
+            return address;
+        }
+
+        public void setAddress(Address address) {
+            this.address = address;
+        }
+    }
+
+    class Address {
+        private String address;
+        private Country country;
+        private String city;
+
+        public String getAddress() {
+            return address;
+        }
+
+        public void setAddress(String address) {
+            this.address = address;
+        }
+
+        public String getCity() {
+            return city;
+        }
+
+        public void setCity(String city) {
+            this.city = city;
+        }
+
+        public Country getCountry() {
+            return country;
+        }
+
+        public void setCountry(Country country) {
+            this.country = country;
+        }
+    }
+
+    class Country {
+        private String iso;
+        private String name;
+        private String displayName;
+
+        public String getIso() {
+            return iso;
+        }
+
+        public void setIso(String iso) {
+            this.iso = iso;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        public void setDisplayName(String displayName) {
+            this.displayName = displayName;
+        }
+    }
 }
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.