001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing.factory;
018:
019: // J2SE dependencies
020: import java.io.Serializable;
021: import java.io.ObjectStreamException;
022: import java.sql.SQLException;
023: import java.util.AbstractSet;
024: import java.util.Collection;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.LinkedHashMap;
028: import java.util.Map;
029: import java.util.NoSuchElementException;
030: import java.util.Set;
031: import java.util.LinkedHashSet;
032: import java.util.logging.Level;
033: import java.util.logging.LogRecord;
034:
035: // OpenGIS dependencies
036: import org.opengis.metadata.Identifier;
037: import org.opengis.referencing.AuthorityFactory;
038: import org.opengis.referencing.FactoryException;
039: import org.opengis.referencing.IdentifiedObject;
040: import org.opengis.referencing.NoSuchIdentifierException;
041: import org.opengis.referencing.NoSuchAuthorityCodeException;
042: import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory; // For javadoc
043:
044: // Geotools dependencies
045: import org.geotools.resources.Utilities;
046:
047: /**
048: * A lazy set of {@linkplain IdentifiedObject identified objects}. This set creates
049: * {@link IdentifiedObject}s from authority codes only when first needed. This class
050: * is typically used as the set returned by implementations of the
051: * {@link CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes
052: * createFromCoordinateReferenceSystemCodes} method. Deferred creation in this case may
053: * have great performance impact since a set may contains about 40 entries (e.g.
054: * transformations from "ED50" (EPSG:4230) to "WGS 84" (EPSG:4326)) while some users
055: * only want to look for the first entry (e.g. the default
056: * {@link org.geotools.referencing.operation.AuthorityBackedFactory} implementation).
057: * <p>
058: * <h3>Exception handling</h3>
059: * If the underlying factory failed to creates an object because of an unsupported
060: * operation method ({@link NoSuchIdentifierException}), the exception is logged with
061: * the {@link Level#FINE FINE} level (because this is a recoverable failure) and
062: * the iteration continue. If the operation creation failed for any other kind of
063: * reason ({@link FactoryException}), then the exception is rethrown as an unchecked
064: * {@link BackingStoreException}. This default behavior can be changed if a subclass
065: * overrides the {@link #isRecoverableFailure isRecoverableFailure} method.
066: * <p>
067: * <h3>Serialization</h3>
068: * Serialization of this class forces the immediate creation of all
069: * {@linkplain IdentifiedObject identified objects} not yet created.
070: * The serialized set is disconnected from the {@linkplain #factory underlying factory}.
071: * <p>
072: * <h3>Thread safety</h3>
073: * This class is not thread-safe.
074: *
075: * @since 2.2
076: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/IdentifiedObjectSet.java $
077: * @version $Id: IdentifiedObjectSet.java 23635 2007-01-01 20:58:15Z desruisseaux $
078: * @author Martin Desruisseaux
079: */
080: public class IdentifiedObjectSet extends AbstractSet implements
081: Serializable {
082: /**
083: * For cross-version compatibility during serialisation.
084: */
085: private static final long serialVersionUID = -4221260663706882719L;
086:
087: /**
088: * The map of object codes (keys), and the actual identified objects (values)
089: * when it has been created. Each entry has a null value until the corresponding
090: * object is created.
091: */
092: private final Map/*<String,IdentifiedObject>*/objects = new LinkedHashMap();
093:
094: /**
095: * The factory to use for creating {@linkplain IdentifiedObject identified objects}
096: * when first needed.
097: */
098: protected final AuthorityFactory factory;
099:
100: /**
101: * Creates an initially empty set. The {@linkplain IdentifiedObject}s
102: * will be created when first needed using the specified factory.
103: *
104: * @param factory The factory to use for deferred {@link IdentifiedObject}s creations.
105: */
106: public IdentifiedObjectSet(final AuthorityFactory factory) {
107: this .factory = factory;
108: }
109:
110: /**
111: * Removes all of the elements from this collection.
112: */
113: public void clear() {
114: objects.clear();
115: }
116:
117: /**
118: * Returns the number of objects available in this set. Note that this
119: * number may decrease during the iteration process if the creation of
120: * some {@linkplain IdentifiedObject identified objects} failed.
121: */
122: public int size() {
123: return objects.size();
124: }
125:
126: /**
127: * Ensures that this collection contains an object for the specified authority code.
128: * The {@linkplain IdentifiedObject identified object} will be created from the specified
129: * code only when first needed. This method returns {@code true} if this set changed as a
130: * result of this call.
131: */
132: public boolean addAuthorityCode(final String code) {
133: final boolean already = objects.containsKey(code);
134: final IdentifiedObject old = (IdentifiedObject) objects.put(
135: code, null);
136: if (old != null) {
137: // A fully created object was already there. Keep it.
138: objects.put(code, old);
139: return false;
140: }
141: return !already;
142: }
143:
144: /**
145: * Ensures that this collection contains the specified object. This set do not allows multiple
146: * objects for the same {@linkplain #getAuthorityCode authority code}. If this set already
147: * contains an object using the same {@linkplain #getAuthorityCode authority code} than the
148: * specified one, then the old object is replaced by the new one even if the objects are not
149: * otherwise identical.
150: */
151: public boolean add(final Object object) {
152: final String code = getAuthorityCode((IdentifiedObject) object);
153: return !Utilities.equals(objects.put(code, object), object);
154: }
155:
156: /**
157: * Returns the identified object for the specified value, {@linkplain #createObject creating}
158: * it if needed.
159: *
160: * @throws BackingStoreException if the object creation failed.
161: */
162: private IdentifiedObject get(final String code)
163: throws BackingStoreException {
164: IdentifiedObject object = (IdentifiedObject) objects.get(code);
165: if (object == null && objects.containsKey(code)) {
166: try {
167: object = createObject(code);
168: objects.put(code, object);
169: } catch (FactoryException exception) {
170: if (!isRecoverableFailure(exception)) {
171: throw new BackingStoreException(exception);
172: }
173: log(exception, code);
174: objects.remove(code);
175: }
176: }
177: return object;
178: }
179:
180: /**
181: * Returns {@code true} if this collection contains the specified object.
182: */
183: public boolean contains(final Object object) {
184: final String code = getAuthorityCode((IdentifiedObject) object);
185: final IdentifiedObject current = get(code);
186: return object.equals(current);
187: }
188:
189: /**
190: * Removes a single instance of the specified element from this collection,
191: * if it is present.
192: */
193: public boolean remove(final Object object) {
194: final String code = getAuthorityCode((IdentifiedObject) object);
195: final IdentifiedObject current = get(code);
196: if (object.equals(current)) {
197: objects.remove(code);
198: return true;
199: }
200: return false;
201: }
202:
203: /**
204: * Removes from this collection all of its elements that are contained in
205: * the specified collection.
206: */
207: public boolean removeAll(final Collection collection) {
208: boolean modified = false;
209: for (final Iterator it = collection.iterator(); it.hasNext();) {
210: if (remove(it.next())) {
211: modified = true;
212: }
213: }
214: return modified;
215: }
216:
217: /**
218: * Returns an iterator over the objects in this set. If the iteration encounter any
219: * kind of {@link FactoryException} other than {@link NoSuchIdentifierException}, then
220: * the exception will be rethrown as an unchecked {@link BackingStoreException}.
221: */
222: public Iterator iterator() {
223: return new Iter(objects.entrySet().iterator());
224: }
225:
226: /**
227: * Ensures that the <var>n</var> first objects in this set are created. This method is
228: * typically invoked after some calls to {@link #addAuthorityCode} in order to make sure
229: * that the {@linkplain #factory underlying factory} is really capable to create at least
230: * one object. {@link FactoryException} (except the ones accepted as
231: * {@linkplain #isRecoverableFailure recoverable failures}) are thrown as if they were never
232: * wrapped into {@link BackingStoreException}.
233: *
234: * @param n The number of object to resolve. If this number is equals or greater than the
235: * {@linkplain #size set's size}, then the creation of all objects is garantee
236: * successful.
237: * @throws FactoryException if an {@linkplain #createObject object creation} failed.
238: */
239: public void resolve(int n) throws FactoryException {
240: if (n > 0)
241: try {
242: for (final Iterator it = iterator(); it.hasNext();) {
243: it.next();
244: if (--n == 0) {
245: break;
246: }
247: }
248: } catch (BackingStoreException exception) {
249: final Throwable cause = exception.getCause();
250: if (cause instanceof FactoryException) {
251: throw (FactoryException) cause;
252: }
253: throw exception;
254: }
255: }
256:
257: /**
258: * Returns the {@linkplain #getAuthorityCode authority code} of all objects in this set.
259: * The returned array contains the codes in iteration order. This method do not trig the
260: * {@linkplain #createObject creation} of any new object.
261: * <p>
262: * This method is typically used together with {@link #setAuthorityCodes} for altering the
263: * iteration order on the basis of authority codes.
264: */
265: public String[] getAuthorityCodes() {
266: final Set codes = objects.keySet();
267: return (String[]) codes.toArray(new String[codes.size()]);
268: }
269:
270: /**
271: * Set the content of this set as an array of authority codes. For any code in the given list,
272: * this method will preserve the corresponding {@linkplain IdentifiedObject identified object}
273: * if it was already created. Other objects will be {@linkplain #createObject created} only
274: * when first needed, as usual in this {@code IdentifiedObjectSet} implementation.
275: * <p>
276: * This method is typically used together with {@link #getAuthorityCodes} for altering the
277: * iteration order on the basis of authority codes. If the specified {@code codes} array
278: * contains the same elements than {@link #getAuthorityCodes} in a different order, then
279: * this method just set the new ordering.
280: *
281: * @see #addAuthorityCode
282: */
283: public void setAuthorityCodes(final String[] codes) {
284: final Map copy = new HashMap(objects);
285: objects.clear();
286: for (int i = 0; i < codes.length; i++) {
287: final String code = codes[i];
288: objects.put(code, (IdentifiedObject) copy.get(code));
289: }
290: }
291:
292: /**
293: * Returns the code to uses as a key for the specified object. The default implementation
294: * returns the code of the first {@linkplain IdentifiedObject#getIdentifiers identifier},
295: * if any, or the code of the{@linkplain IdentifiedObject#getName primary name} otherwise.
296: * Subclasses may overrides this method if they want to use a different key for this set.
297: */
298: protected String getAuthorityCode(final IdentifiedObject object) {
299: final Identifier id;
300: final Set identifiers = object.getIdentifiers();
301: if (identifiers != null && !identifiers.isEmpty()) {
302: id = (Identifier) identifiers.iterator().next();
303: } else {
304: id = object.getName();
305: }
306: return id.getCode();
307: }
308:
309: /**
310: * Creates an object for the specified authority code. This method is invoked during the
311: * iteration process if an object was not already created. The default implementation invokes
312: * <code>{@linkplain #factory}.{@link AuthorityFactory#createObject createObject}(code)</code>.
313: * Subclasses may override this method if they want to invoke a more specific method.
314: */
315: protected IdentifiedObject createObject(final String code)
316: throws FactoryException {
317: return factory.createObject(code);
318: }
319:
320: /**
321: * Returns {@code true} if the specified exception should be handled as a recoverable failure.
322: * This method is invoked during the iteration process if the factory failed to create some
323: * object. If this method returns {@code true} for the given exception, then the exception
324: * will be logged in the {@linkplain AbstractAuthorityFactory#LOGGER Geotools factory logger}
325: * with the {@link Level#FINE FINE} level. If this method returns {@code false}, then the
326: * exception will be retrown as a {@link BackingStoreException}. The default implementation
327: * returns {@code true} only for {@link NoSuchIdentifierException} (not to be confused with
328: * {@link NoSuchAuthorityCodeException}).
329: */
330: protected boolean isRecoverableFailure(
331: final FactoryException exception) {
332: return (exception instanceof NoSuchIdentifierException);
333: }
334:
335: /**
336: * Log an message for the specified exception.
337: *
338: * @todo Localize.
339: */
340: static void log(final FactoryException exception, final String code) {
341: final LogRecord record = new LogRecord(Level.FINE,
342: "Failed to create an object for code \"" + code + "\".");
343: record.setSourceClassName(IdentifiedObjectSet.class.getName());
344: record.setSourceMethodName("createObject");
345: record.setThrown(exception);
346: AbstractAuthorityFactory.LOGGER.log(record);
347: }
348:
349: /**
350: * Returns a serializable copy of this set. This method is invoked automatically during
351: * serialization. The serialised set of identified objects is disconnected from the
352: * {@linkplain #factory underlying factory}.
353: */
354: protected Object writeReplace() throws ObjectStreamException {
355: return new LinkedHashSet(this );
356: }
357:
358: /**
359: * The iterator over the entries in the enclosing set. This iterator will creates the
360: * {@linkplain IdentifiedObject identified objects} when first needed.
361: *
362: * @version $Id: IdentifiedObjectSet.java 23635 2007-01-01 20:58:15Z desruisseaux $
363: * @author Martin Desruisseaux
364: */
365: private final class Iter implements Iterator {
366: /**
367: * The iterator over the entries from the underlying map.
368: */
369: private final Iterator iterator;
370:
371: /**
372: * The next object to returns, or {@code null} if the iteration is over.
373: */
374: private IdentifiedObject element;
375:
376: /**
377: * Creates a new instance of this iterator.
378: */
379: public Iter(final Iterator iterator) {
380: this .iterator = iterator;
381: toNext();
382: }
383:
384: /**
385: * Moves to the next element.
386: *
387: * @throws BackingStoreException if the underlying factory failed to creates the
388: * coordinate operation.
389: */
390: private void toNext() throws BackingStoreException {
391: while (iterator.hasNext()) {
392: final Map.Entry entry = (Map.Entry) iterator.next();
393: element = (IdentifiedObject) entry.getValue();
394: if (element == null) {
395: final String code = (String) entry.getKey();
396: try {
397: element = createObject(code);
398: } catch (FactoryException exception) {
399: if (!isRecoverableFailure(exception)) {
400: throw new BackingStoreException(exception);
401: }
402: log(exception, code);
403: iterator.remove();
404: continue;
405: }
406: entry.setValue(element);
407: }
408: return; // Element found.
409: }
410: element = null; // No more element found.
411: }
412:
413: /**
414: * Returns {@code true} if there is more elements.
415: */
416: public boolean hasNext() {
417: return element != null;
418: }
419:
420: /**
421: * Returns the next element.
422: *
423: * @throws NoSuchElementException if there is no more operations in the set.
424: */
425: public Object next() throws NoSuchElementException {
426: final IdentifiedObject next = element;
427: if (next == null) {
428: throw new NoSuchElementException();
429: }
430: toNext();
431: return next;
432: }
433:
434: /**
435: * Removes the last element from the underlying set.
436: */
437: public void remove() {
438: iterator.remove();
439: }
440: }
441: }
|