View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.sesame.behaviours;
30  
31  import info.aduna.iteration.CloseableIteration;
32  
33  import java.util.AbstractSequentialList;
34  import java.util.ArrayList;
35  import java.util.ListIterator;
36  import java.util.NoSuchElementException;
37  
38  import org.openrdf.elmo.Entity;
39  import org.openrdf.elmo.Mergeable;
40  import org.openrdf.elmo.Refreshable;
41  import org.openrdf.elmo.annotations.intercepts;
42  import org.openrdf.elmo.annotations.rdf;
43  import org.openrdf.elmo.exceptions.ElmoIOException;
44  import org.openrdf.elmo.exceptions.ElmoPersistException;
45  import org.openrdf.elmo.sesame.SesameManager;
46  import org.openrdf.elmo.sesame.iterators.ElmoIteration;
47  import org.openrdf.elmo.sesame.roles.SesameEntity;
48  import org.openrdf.model.BNode;
49  import org.openrdf.model.Resource;
50  import org.openrdf.model.Statement;
51  import org.openrdf.model.URI;
52  import org.openrdf.model.Value;
53  import org.openrdf.model.ValueFactory;
54  import org.openrdf.model.vocabulary.RDF;
55  import org.openrdf.repository.RepositoryConnection;
56  import org.openrdf.repository.RepositoryException;
57  import org.openrdf.repository.contextaware.ContextAwareConnection;
58  
59  /**
60   * Java instance for rdf:List as a familiar interface to manipulate this List.
61   * This implemention can only be modified when in autoCommit (autoFlush), or
62   * when read uncommitted is supported.
63   * 
64   * @author James Leigh
65   */
66  @rdf("http://www.w3.org/1999/02/22-rdf-syntax-ns#List")
67  public class SesameList extends AbstractSequentialList<Object> implements
68  		java.util.List<Object>, Refreshable, Mergeable {
69  
70  	SesameManager manager;
71  
72  	Resource resource;
73  
74  	private int _size = -1;
75  
76  	private SesameList parent;
77  
78  	public SesameList(Entity bean) {
79  		SesameEntity sbean = (SesameEntity) bean;
80  		this.manager = sbean.getSesameManager();
81  		this.resource = sbean.getSesameResource();
82  	}
83  
84  	public void refresh() {
85  		_size = -1;
86  		if (parent != null)
87  			parent.refresh();
88  	}
89  
90  	public void merge(Object source) {
91  		if (source instanceof java.util.List) {
92  			clear();
93  			addAll((java.util.List) source);
94  		}
95  	}
96  
97  	ValueFactory getValueFactory() {
98  		RepositoryConnection conn = manager.getConnection();
99  		return conn.getRepository().getValueFactory();
100 	}
101 
102 	private ElmoIteration<Statement, Value> getStatements(Resource subj,
103 			URI pred, Value obj) {
104 		try {
105 			CloseableIteration<? extends Statement, RepositoryException> stmts;
106 			ContextAwareConnection conn = manager.getConnection();
107 			stmts = conn.getStatements(subj, pred, obj);
108 			return new ElmoIteration<Statement, Value>(stmts) {
109 				@Override
110 				protected Value convert(Statement stmt) throws Exception {
111 					return stmt.getObject();
112 				}
113 			};
114 		} catch (RepositoryException e) {
115 			throw new ElmoIOException(e);
116 		}
117 	}
118 
119 	void addStatement(Resource subj, URI pred, Value obj) {
120 		if (obj == null)
121 			return;
122 		try {
123 			ContextAwareConnection conn = manager.getConnection();
124 			conn.add(subj, pred, obj);
125 		} catch (RepositoryException e) {
126 			throw new ElmoPersistException(e);
127 		}
128 	}
129 
130 	void removeStatements(Resource subj, URI pred, Value obj) {
131 		try {
132 			ContextAwareConnection conn = manager.getConnection();
133 			conn.remove(subj, pred, obj);
134 		} catch (RepositoryException e) {
135 			throw new ElmoPersistException(e);
136 		}
137 	}
138 
139 	@Override
140 	public int size() {
141 		if (_size < 0) {
142 			synchronized (this) {
143 				if (_size < 0) {
144 					Resource list = resource;
145 					int size;
146 					for (size = 0; list != null && !list.equals(RDF.NIL); size++) {
147 						Resource nlist = getRest(list);
148 						if (nlist == null && getFirst(list) == null)
149 							break;
150 						list = nlist;
151 					}
152 					_size = size;
153 				}
154 			}
155 		}
156 		return _size;
157 	}
158 
159 	@Override
160 	public ListIterator<Object> listIterator(final int index) {
161 		return new ListIterator<Object>() {
162 			private ArrayList<Resource> prevLists = new ArrayList<Resource>();
163 
164 			private boolean removed;
165 
166 			Resource list;
167 			{
168 				for (int i = 0; i < index; i++) {
169 					next();
170 				}
171 			}
172 
173 			public void add(Object o) {
174 				RepositoryConnection conn = manager.getConnection();
175 				try {
176 					boolean autoCommit = conn.isAutoCommit();
177 					if (autoCommit)
178 						conn.setAutoCommit(false);
179 					if (resource.equals(RDF.NIL)) {
180 						// size == 0
181 						throw new ElmoPersistException(
182 								"cannot add a value to the nil list");
183 						/*
184 						 * list = _id = getValueFactory().createBNode();
185 						 * addStatement(list, RDF.FIRST,
186 						 * SesameProperty.createValue(List.this, o));
187 						 * addStatement(list, RDF.REST, RDF.NIL);
188 						 */
189 					}
190 					Value value = o == null ? null : manager.getValue(o);
191 					if (getFirst(resource) == null) {
192 						// size == 0
193 						list = resource;
194 						addStatement(list, RDF.FIRST, value);
195 						addStatement(list, RDF.REST, RDF.NIL);
196 					} else if (list == null) {
197 						// index = 0
198 						Value first = getFirst(resource);
199 						Resource rest = getRest(resource);
200 						BNode newList = getValueFactory().createBNode();
201 						addStatement(newList, RDF.FIRST, first);
202 						addStatement(newList, RDF.REST, rest);
203 						removeStatements(resource, RDF.FIRST, first);
204 						removeStatements(resource, RDF.REST, rest);
205 						addStatement(resource, RDF.FIRST, value);
206 						addStatement(resource, RDF.REST, newList);
207 					} else if (!list.equals(RDF.NIL)) {
208 						Resource rest = getRest(list);
209 						BNode newList = getValueFactory().createBNode();
210 						removeStatements(list, RDF.REST, rest);
211 						addStatement(list, RDF.REST, newList);
212 						addStatement(newList, RDF.FIRST, value);
213 						addStatement(newList, RDF.REST, rest);
214 					} else {
215 						// index == size
216 						throw new NoSuchElementException();
217 					}
218 					if (autoCommit)
219 						conn.setAutoCommit(true);
220 					refresh();
221 				} catch (RepositoryException e) {
222 					throw new ElmoPersistException(e);
223 				}
224 			}
225 
226 			public void set(Object o) {
227 				RepositoryConnection conn = manager.getConnection();
228 				try {
229 					boolean autoCommit = conn.isAutoCommit();
230 					if (autoCommit)
231 						conn.setAutoCommit(false);
232 					if (resource.equals(RDF.NIL)) {
233 						// size == 0
234 						throw new NoSuchElementException();
235 					} else if (list.equals(RDF.NIL)) {
236 						// index = size
237 						throw new NoSuchElementException();
238 					} else {
239 						Value first = getFirst(list);
240 						removeStatements(list, RDF.FIRST, first);
241 						if (o != null) {
242 							Value obj = manager.getValue(o);
243 							addStatement(list, RDF.FIRST, obj);
244 						}
245 					}
246 					if (autoCommit)
247 						conn.setAutoCommit(true);
248 					refresh();
249 				} catch (RepositoryException e) {
250 					throw new ElmoPersistException(e);
251 				}
252 			}
253 
254 			public void remove() {
255 				RepositoryConnection conn = manager.getConnection();
256 				try {
257 					boolean autoCommit = conn.isAutoCommit();
258 					if (autoCommit)
259 						conn.setAutoCommit(false);
260 					if (prevLists.size() < 1) {
261 						// remove index == 0
262 						Value first = getFirst(list);
263 						removeStatements(list, RDF.FIRST, first);
264 						Resource next = getRest(list);
265 						first = getFirst(next);
266 						Resource rest = getRest(next);
267 						removeStatements(list, RDF.REST, next);
268 						if (first != null) {
269 							removeStatements(next, RDF.FIRST, first);
270 							addStatement(list, RDF.FIRST, first);
271 						}
272 						if (rest != null) {
273 							removeStatements(next, RDF.REST, rest);
274 							addStatement(list, RDF.REST, rest);
275 						}
276 					} else {
277 						// remove index > 0
278 						Resource removedList = list;
279 						list = prevLists.remove(prevLists.size() - 1);
280 						Value first = getFirst(removedList);
281 						Resource rest = getRest(removedList);
282 						removeStatements(removedList, RDF.FIRST, first);
283 						removeStatements(removedList, RDF.REST, rest);
284 						removeStatements(list, RDF.REST, removedList);
285 						addStatement(list, RDF.REST, rest);
286 					}
287 					if (autoCommit)
288 						conn.setAutoCommit(true);
289 					removed = true;
290 					refresh();
291 				} catch (RepositoryException e) {
292 					throw new ElmoPersistException(e);
293 				}
294 			}
295 
296 			public boolean hasNext() {
297 				Resource next;
298 				if (list == null) {
299 					next = resource;
300 				} else {
301 					next = getRest(list);
302 				}
303 				return getFirst(next) != null;
304 			}
305 
306 			public Object next() {
307 				if (list == null) {
308 					list = resource;
309 				} else if (!removed) {
310 					prevLists.add(list);
311 					list = getRest(list);
312 				} else {
313 					removed = false;
314 				}
315 				Value first = getFirst(list);
316 				if (first == null)
317 					throw new NoSuchElementException();
318 				return createInstance(first);
319 			}
320 
321 			public int nextIndex() {
322 				if (list == null)
323 					return 0;
324 				return prevLists.size() + 1;
325 			}
326 
327 			public int previousIndex() {
328 				return prevLists.size() - 1;
329 			}
330 
331 			public boolean hasPrevious() {
332 				return prevLists.size() > 0;
333 			}
334 
335 			public Object previous() {
336 				list = prevLists.remove(prevLists.size() - 1);
337 				removed = false;
338 				Value first = getFirst(list);
339 				if (first == null)
340 					throw new NoSuchElementException();
341 				return createInstance(first);
342 			}
343 
344 			private Object createInstance(Value first) {
345 				return manager.getInstance(first);
346 			}
347 		};
348 	}
349 
350 	@Override
351 	@intercepts(method="toString",argc=0)
352 	public String toString() {
353 		return super.toString();
354 	}
355 
356 	Value getFirst(Resource list) {
357 		if (list == null)
358 			return null;
359 		ElmoIteration<Statement, Value> stmts;
360 		stmts = getStatements(list, RDF.FIRST, null);
361 		try {
362 			if (stmts.hasNext())
363 				return stmts.next();
364 			return null;
365 		} finally {
366 			stmts.close();
367 		}
368 	}
369 
370 	Resource getRest(Resource list) {
371 		if (list == null)
372 			return null;
373 		ElmoIteration<Statement, Value> stmts;
374 		stmts = getStatements(list, RDF.REST, null);
375 		try {
376 			if (stmts.hasNext())
377 				return (Resource) stmts.next();
378 			return null;
379 		} finally {
380 			stmts.close();
381 		}
382 	}
383 }