1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.loganalysis.item;
17 
18 import org.json.JSONException;
19 import org.json.JSONObject;
20 
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 
26 /**
27  * An implementation of the {@link IItem} interface which implements helper methods.
28  */
29 public class GenericItem implements IItem {
30     private Map<String, Object> mAttributes = new HashMap<String, Object>();
31     private Set<String> mAllowedAttributes;
32 
GenericItem(Set<String> allowedAttributes)33     protected GenericItem(Set<String> allowedAttributes) {
34         mAllowedAttributes = new HashSet<String>();
35         mAllowedAttributes.addAll(allowedAttributes);
36     }
37 
GenericItem(Set<String> allowedAttributes, Map<String, Object> attributes)38     protected GenericItem(Set<String> allowedAttributes, Map<String, Object> attributes) {
39         this(allowedAttributes);
40 
41         for (Map.Entry<String, Object> entry : attributes.entrySet()) {
42             setAttribute(entry.getKey(), entry.getValue());
43         }
44     }
45 
46     /**
47      * {@inheritDoc}
48      */
49     @Override
merge(IItem other)50     public IItem merge(IItem other) throws ConflictingItemException {
51         if (this == other) {
52             return this;
53         }
54         if (other == null || getClass() != other.getClass()) {
55             throw new ConflictingItemException("Conflicting class types");
56         }
57 
58         return new GenericItem(mAllowedAttributes, mergeAttributes(other, mAllowedAttributes));
59     }
60 
61     /**
62      * Merges the attributes from the item and another and returns a Map of the merged attributes.
63      * <p>
64      * Goes through each field in the item preferring non-null attributes over null attributes.
65      * </p>
66      *
67      * @param other The other item
68      * @return A Map from Strings to Objects containing merged attributes.
69      * @throws ConflictingItemException If the two items are not consistent.
70      */
mergeAttributes(IItem other, Set<String> attributes)71     protected Map<String, Object> mergeAttributes(IItem other, Set<String> attributes)
72             throws ConflictingItemException {
73         if (this == other) {
74             return mAttributes;
75         }
76         if (other == null || getClass() != other.getClass()) {
77             throw new ConflictingItemException("Conflicting class types");
78         }
79 
80         GenericItem item = (GenericItem) other;
81         Map<String, Object> mergedAttributes = new HashMap<String, Object>();
82         for (String attribute : attributes) {
83             mergedAttributes.put(attribute,
84                     mergeObjects(getAttribute(attribute), item.getAttribute(attribute)));
85         }
86         return mergedAttributes;
87     }
88 
89     /** {@inheritDoc} */
90     @Override
isConsistent(IItem other)91     public boolean isConsistent(IItem other) {
92         if (this == other) {
93             return true;
94         }
95         if (other == null || getClass() != other.getClass()) {
96             return false;
97         }
98 
99         GenericItem item = (GenericItem) other;
100         for (String attribute : mAllowedAttributes) {
101             if (!areConsistent(getAttribute(attribute), item.getAttribute(attribute))) {
102                 return false;
103             }
104         }
105         return true;
106     }
107 
108     /**
109      * {@inheritDoc}
110      */
111     @Override
equals(Object other)112     public boolean equals(Object other) {
113         if (this == other) {
114             return true;
115         }
116         if (other == null || getClass() != other.getClass()) {
117             return false;
118         }
119 
120         GenericItem item = (GenericItem) other;
121         for (String attribute : mAllowedAttributes) {
122             if (!areEqual(getAttribute(attribute), item.getAttribute(attribute))) {
123                 return false;
124             }
125         }
126         return true;
127     }
128 
129     /** {@inheritDoc} */
130     @Override
hashCode()131     public int hashCode() {
132         int result = 13;
133         for (String attribute : mAllowedAttributes) {
134             Object val = getAttribute(attribute);
135             result += 37 * (val == null ? 0 : val.hashCode());
136         }
137         return result;
138     }
139 
140     /**
141      * {@inheritDoc}
142      * <p>
143      * This method will only return a JSON object with attributes that the JSON library knows how to
144      * encode, along with attributes which implement the {@link IItem} interface.  If objects are
145      * stored in this class that fall outside this category, they must be encoded outside of this
146      * method.
147      * </p>
148      */
149     @Override
toJson()150     public JSONObject toJson() {
151         JSONObject object = new JSONObject();
152         for (Map.Entry<String, Object> entry : mAttributes.entrySet()) {
153             final String key = entry.getKey();
154             final Object attribute = entry.getValue();
155             try {
156                 if (attribute != null && attribute instanceof IItem) {
157                     object.put(key, ((IItem) attribute).toJson());
158                 } else {
159                     object.put(key, attribute);
160                 }
161             } catch (JSONException e) {
162                 // Ignore
163             }
164         }
165         return object;
166     }
167 
168     /**
169      * Set an attribute to a value.
170      *
171      * @param attribute The name of the attribute.
172      * @param value The value.
173      * @throws IllegalArgumentException If the attribute is not in allowedAttributes.
174      */
setAttribute(String attribute, Object value)175     protected void setAttribute(String attribute, Object value) throws IllegalArgumentException {
176         if (!mAllowedAttributes.contains(attribute)) {
177             throw new IllegalArgumentException();
178         }
179         mAttributes.put(attribute, value);
180     }
181 
182     /**
183      * Get the value of an attribute.
184      *
185      * @param attribute The name of the attribute.
186      * @return The value or null if the attribute has not been set.
187      * @throws IllegalArgumentException If the attribute is not in allowedAttributes.
188      */
getAttribute(String attribute)189     protected Object getAttribute(String attribute) throws IllegalArgumentException {
190         if (!mAllowedAttributes.contains(attribute)) {
191             throw new IllegalArgumentException();
192         }
193         return mAttributes.get(attribute);
194     }
195 
196     /**
197      * Helper method to return if two objects are equal.
198      *
199      * @param object1 The first object
200      * @param object2 The second object
201      * @return True if object1 and object2 are both null or if object1 is equal to object2, false
202      * otherwise.
203      */
areEqual(Object object1, Object object2)204     static protected boolean areEqual(Object object1, Object object2) {
205         return object1 == null ? object2 == null : object1.equals(object2);
206     }
207 
208     /**
209      * Helper method to return if two objects are consistent.
210      *
211      * @param object1 The first object
212      * @param object2 The second object
213      * @return True if either object1 or object2 is null or if object1 is equal to object2, false if
214      * both objects are not null and not equal.
215      */
areConsistent(Object object1, Object object2)216     static protected boolean areConsistent(Object object1, Object object2) {
217         return object1 == null || object2 == null ? true : object1.equals(object2);
218     }
219 
220     /**
221      * Helper method used for merging two objects.
222      *
223      * @param object1 The first object
224      * @param object2 The second object
225      * @return If both objects are null, then null, else the non-null item if both items are equal.
226      * @throws ConflictingItemException If both objects are not null and they are not equal.
227      */
mergeObjects(Object object1, Object object2)228     static protected Object mergeObjects(Object object1, Object object2)
229             throws ConflictingItemException {
230         if (!areConsistent(object1, object2)) {
231             throw new ConflictingItemException(String.format("%s conflicts with %s", object1,
232                     object2));
233         }
234         return object1 == null ? object2 : object1;
235     }
236 }
237