View Javadoc

1   package org.sourceforge.jemm.client;
2   
3   import java.util.Map.Entry;
4   
5   import org.sourceforge.jemm.client.shared.Encodable;
6   import org.sourceforge.jemm.client.shared.ValueEncoder;
7   import org.sourceforge.jemm.database.ClassId;
8   import org.sourceforge.jemm.database.ClassInfo;
9   import org.sourceforge.jemm.database.ClientId;
10  import org.sourceforge.jemm.database.ClientThreadId;
11  import org.sourceforge.jemm.database.Database;
12  import org.sourceforge.jemm.database.EnumId;
13  import org.sourceforge.jemm.database.EnumInfo;
14  import org.sourceforge.jemm.database.FieldInfo;
15  import org.sourceforge.jemm.database.FieldType;
16  import org.sourceforge.jemm.database.GetObjectResp;
17  import org.sourceforge.jemm.database.LockAcquiredListener;
18  import org.sourceforge.jemm.database.ObjectSyncData;
19  import org.sourceforge.jemm.database.ObjectSyncResp;
20  import org.sourceforge.jemm.database.StructureModifiedException;
21  import org.sourceforge.jemm.lifecycle.TypeRequest;
22  import org.sourceforge.jemm.lifecycle.TypeResponse;
23  import org.sourceforge.jemm.types.ID;
24  import org.sourceforge.jemm.util.JEMMObject;
25  
26  /**
27   * An Adapter that maps the ObjectState fields onto actual objects, which
28   * are created and stores the objects it creates into a memory sensitive
29   * cache.
30   * 
31   * When objects are garbage collected the reference is cleared. Otherwise
32   * requests for objects with an id already pulled will return the exact same object.
33   * 
34   * 
35   * @author Paul Keeble
36   *
37   */
38  public class DatabaseAdapter implements ObjectDatabase {
39  	protected final ClientId clientId = new ClientId("CLIENT");
40  
41  	protected final Database database;
42  	protected JEMMObjectFactory objectFactory;
43  	
44  	protected JEMMObjectCreator objectCreator;
45  	
46  
47  	public DatabaseAdapter(final Database database,JEMMObjectCreator creator) {
48  		this.database = database;
49  		this.objectCreator = creator;
50  		
51  		if(objectCreator == null)
52  			objectCreator = new JEMMObjectCreatorImpl(null,this);
53  		
54  		objectFactory = new JEMMObjectFactory() {
55  
56  			@Override
57  			protected JEMMObject createValue(ID id) {
58  				GetObjectResp resp = database.getObject(clientId,id);
59  				ClassInfo ci = database.getClassInfo(clientId,resp.getClassId());
60  				return objectCreator.createObject(resp,ci);
61  			}			
62  		};
63  	}
64  	
65  	public DatabaseAdapter(Database database) {
66  		this(database,null);
67  	}
68  
69  	public void setObjectCreator(JEMMObjectCreator creator) {
70  		this.objectCreator = creator;
71  	}
72  
73  	/**
74  	 * Looks up the Object with ID jemmId and converts from the underlying
75  	 * Delegate database into the actual JEMMObject from the response data.
76  	 * 
77  	 * This  routine:
78  	 * - Converts any IDs to trackedIDs
79  	 * - Creates the object based on the known class information
80  	 * - Sets up the ShadowObject
81  	 * - Sets up the fields on the object/ShadowData 
82  	 * - caches the resulting objects mapped by ID
83  	 */
84  	@Override
85  	public JEMMObject getObject(ID jemmId) {		
86  		return objectFactory.create(jemmId);
87  	}
88  	
89  	public JEMMObject getRefreshedObject(ID jemmId) {
90  		if(objectFactory.contains(jemmId)) {
91  			JEMMObject obj = objectFactory.create(jemmId);
92  			refreshObject(obj);
93  			return obj;
94  		}
95  		return getObject(jemmId);
96  	}
97  	
98  	/**
99  	 * Refreshes an objects state by:
100 	 * - Retrieving the object again from the database and setting all primitives to the new values
101 	 * - setting all uninitialised Object fields to the new values
102 	 * - For initialised fields that are objects either:
103 	 *   *) Matches the ID return from step 1 and refresh the object recursively
104 	 *   *) Does not match the ID, the new object is retrieved and set as the value.
105 	 */
106 	public void refreshObject(JEMMObject obj) {
107 		Entity mainObjectEntity = new Entity(obj);
108 		ObjectAccessor oa = new ObjectAccessor(obj,null);
109 		GetObjectResp response = database.getObject(clientId,mainObjectEntity.getID());
110 		objectCreator.refreshPrimitiveState(obj, response);		
111 		refreshInitialisedFields(oa,response);
112 	}
113 	
114 	private void refreshInitialisedFields(ObjectAccessor oa, GetObjectResp response) {
115 		for(Entry<FieldKey,JEMMObject> entry : oa.getInitialisedObjectFields().entrySet()) {
116 			
117 			ID objID = (entry.getValue()==null) ? null : new Entity(entry.getValue()).getID();
118 			FieldKey fKey = entry.getKey();
119 			FieldInfo fi = new FieldInfo(fKey.getName(),fKey.getClazz(),FieldType.OBJECT);
120 			
121 			ID refreshValue = (ID)response.fieldValues.get(fi);
122 			
123 			if(refreshValue==null && objID == null) {
124 				continue;
125 			} else if(refreshValue == null && objID!=null) {
126 				oa.setField(fKey.getClazz(), fKey.getName(), null);
127 			} else if(refreshValue.equals(objID)) {
128 				refreshObject(entry.getValue());
129 			} else {
130 				JEMMObject newValue = getRefreshedObject(refreshValue);
131 				oa.setField(fKey.getClazz(), fKey.getName(), newValue);
132 			}
133 		}
134 	}
135 
136 	/**
137 	 * Takes all the data from the syncData object and creates an ObjectSyncData
138 	 * packet which is then passed to the delegated Database.
139 	 * @param syncData the JEMMObject data to use.
140 	 * 
141 	 */
142 	@Override
143 	public void synchroniseObject(JEMMObject jo) {
144 		
145 		ObjectSyncData syncData = objectCreator.getSyncData(jo);
146 
147 		ObjectSyncResp response = database.synchroniseObject(clientId, syncData.jemmId, syncData);
148 		objectCreator.syncResponseUpdateObject(jo, response);
149 	}
150 	
151 	@Override
152 	public void setRoot(String rootName, JEMMObject newValue) {
153 		if(newValue == null)
154 			database.setRoot(clientId,rootName, null);
155 		else {
156 			ID id = new Entity(newValue).getID();
157 			database.setRoot(clientId,rootName, id);			
158 		}		
159 	}
160 
161 	@Override
162 	public JEMMObject setRootIfNull(String rootName, JEMMObject newValue) {
163 		if(newValue == null)
164 			throw new NullPointerException("New value cannot be null");
165 		ID newId = new Entity(newValue).getID();		
166 		ID postSetValueId =  database.setRootIfNull(clientId,rootName, newId);
167 		return getObject(postSetValueId);
168 	}
169 	
170 	@Override
171 	public JEMMObject getRoot(String rootName) {
172 		ID rootID = database.getRoot(clientId,rootName);
173 		if(rootID==null)
174 			return null;
175 		else
176 			return getObject(rootID);
177 	}
178 	
179 	public int cacheSize() {
180 		return objectFactory.size();
181 	}
182 
183 	@Override
184 	public void acquireLock(ClientThreadId threadId, ID jemmId) {
185 		database.acquireLock(threadId, jemmId);
186 	}
187 
188 	@Override
189 	public void setClientLockAcquiredListener(ClientId clientId,
190 			LockAcquiredListener listener) {
191 		database.setClientLockAcquiredListener(clientId, listener);
192 	}
193 
194 	@Override
195 	public ClassInfo getClassInfo(ClassId classId) {
196 		return database.getClassInfo(clientId,classId);
197 	}
198 
199 	@Override
200 	public EnumInfo getEnumInfo(EnumId enumId) {
201 		return database.getEnumInfo(clientId,enumId);
202 	}
203 
204 	@Override
205 	public ID newObject(ClassId classId,JEMMObject target) {
206 		ID id = database.newObject(clientId,classId);
207 		objectFactory.put(id,target);
208 		return id;
209 	}
210 
211 	@Override
212 	public ClassId registerClass(ClassInfo classInfo) throws StructureModifiedException {	    
213 		return database.registerClass(clientId,classInfo);
214 	}
215 
216 	@Override
217 	public EnumId registerEnum(EnumInfo enumInfo) throws StructureModifiedException {
218 		return database.registerEnum(clientId,enumInfo);
219 	}
220 
221 	@Override
222 	public void releaseLock(ClientThreadId threadId, ID jemmId) {
223 		database.releaseLock(threadId, jemmId);
224 	}
225 
226 	@Override
227 	public void removeLockAcquiredListener(ClientId clientId) {
228 		database.removeLockAcquiredListener(clientId);
229 	}
230 	
231 	abstract class FieldValueEncoder extends ValueEncoder {
232 		@SuppressWarnings("unchecked")
233 		@Override public <K> K encode(K value) {
234 			if(value == null)
235 				return value;
236 			if(value instanceof Encodable) {
237 				return (K) ((Encodable) value).encode(this);
238 			} 
239 			
240 			if(value instanceof Object[]) {
241 				Object[] arr = ((Object[]) value).clone();
242 				for(int i=0; i<arr.length; i++)
243 					arr[i] = encode(arr[i]);
244 				return (K) arr;
245 			}
246 
247 			return encodeSingle(value);
248 		}
249 
250 		public abstract <K> K encodeSingle(K value);
251 	}
252 	
253 	ValueEncoder toIdEncoder = new FieldValueEncoder() {
254 		@SuppressWarnings("unchecked")
255 		@Override public <K> K encodeSingle(K value) {
256 			if(value instanceof JEMMObject)
257 				return (K) (new Entity((JEMMObject) value)).getID();				
258 			else
259 				return value;
260 		}
261 	};
262 
263 	ValueEncoder toObjectEncoder = new FieldValueEncoder() {
264 		@SuppressWarnings("unchecked")
265 		@Override public <K> K encodeSingle(K value) {
266 			if(value instanceof ID)
267 				return (K) getObject((ID) value);
268 			else
269 				return value;
270 		}
271 	};
272 	
273 	@Override
274 	public TypeResponse<?> processTypeRequest(JEMMObject obj,ClassId classId,TypeRequest<?> request) {
275 		
276 		ID objId = new Entity(obj).getID();		
277 		TypeRequest<?> encodedRequest = toIdEncoder.encode(request);
278 		TypeResponse<?> encodedResp = database.processTypeRequest(clientId, classId, 
279 				objId,encodedRequest);
280 		
281 		return toObjectEncoder.encode(encodedResp);
282 	}
283 	
284 	public void shutdown() {
285 		objectFactory.shutdown();
286 	}
287 }