Source Code Cross Referenced for MetadataAccessor.java in  » GIS » GeoTools-2.4.1 » org » geotools » image » io » metadata » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.image.io.metadata 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         *    GeoTools - OpenSource mapping toolkit
003:         *    http://geotools.org
004:         *    (C) 2007, GeoTools Project Managment Committee (PMC)
005:         *    (C) 2007, Geomatys
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.image.io.metadata;
018:
019:        // J2SE dependencies
020:        import java.lang.reflect.Array;
021:        import java.util.ArrayList;
022:        import java.util.Collection;
023:        import java.util.Collections;
024:        import java.util.Date;
025:        import java.util.Iterator;
026:        import java.util.LinkedHashSet;
027:        import java.util.List;
028:        import java.util.NoSuchElementException;
029:        import java.util.StringTokenizer;
030:        import java.util.logging.Level;
031:        import java.util.logging.LogRecord;
032:        import javax.imageio.metadata.IIOMetadataNode;
033:        import org.w3c.dom.Element;
034:        import org.w3c.dom.Node;
035:        import org.w3c.dom.NodeList;
036:
037:        // Geotools dependencies
038:        import org.geotools.resources.XMath;
039:        import org.geotools.resources.Utilities;
040:        import org.geotools.resources.i18n.Errors;
041:        import org.geotools.resources.i18n.ErrorKeys;
042:        import org.geotools.resources.OptionalDependencies;
043:        import org.geotools.util.UnsupportedImplementationException;
044:
045:        /**
046:         * Base class for {@linkplain GeographicMetadata geographic metadata} parsers. This class
047:         * provides convenience methods for encoding and decoding metadata information. A metadata
048:         * root {@linkplain Node node} is specified at construction time, together with a path to
049:         * the {@linkplain Element element} of interest. Example of valid paths:
050:         * <p>
051:         * <ul>
052:         *   <li>{@code "CoordinateReferenceSystem/Datum"}</li>
053:         *   <li>{@code "CoordinateReferenceSystem/CoordinateSystem"}</li>
054:         *   <li>{@code "GridGeometry/Envelope"}</li>
055:         * </ul>
056:         * <p>
057:         * In addition, some elements contains an arbitrary amount of childs. The path to child
058:         * elements can also be specified to the constructor. Examples (note that the constructor
059:         * expects paths relative to the parent; we show absolute paths below for completness):
060:         * <p>
061:         * <ul>
062:         *   <li>{@code "CoordinateReferenceSystem/CoordinateSystem/Axis"}</li>
063:         *   <li>{@code "GridGeometry/Envelope/CoordinateValues"}</li>
064:         *   <li>{@code "SampleDimensions/SampleDimension"}</li>
065:         * </ul>
066:         *
067:         * The {@code get} and {@code set} methods defined in this class will operate on the
068:         * <cite>selected</cite> {@linkplain Element element}, which may be either the one
069:         * specified at construction time, or one of its childs. The element can be selected
070:         * by {@link #selectParent} (the default) or {@link #selectChild}.
071:         * <p>
072:         * The example below creates an accessor for a node called {@code "CoordinateSystem"}
073:         * which is expected to have childs called {@code "Axis"}:
074:         *
075:         * <blockquote><pre>
076:         * MetadataAccessor accessor = new MetadataAccessor(metadata,
077:         *         "CoordinateReferenceSystem/CoordinateSystem", "Axis");
078:         *
079:         * accessor.selectParent();
080:         * String csName = accessor.getString("name");
081:         *
082:         * accessor.selectChild(0);
083:         * String firstAxisName = accessor.getString("name");
084:         * </pre></blockquote>
085:         *
086:         * @since 2.4
087:         * @version $Id: MetadataAccessor.java 27583 2007-10-23 11:29:26Z desruisseaux $
088:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/metadata/MetadataAccessor.java $
089:         * @author Martin Desruisseaux
090:         */
091:        public class MetadataAccessor {
092:            /**
093:             * The separator between names in a node path.
094:             */
095:            private static final char SEPARATOR = '/';
096:
097:            /**
098:             * The owner of this accessor.
099:             */
100:            private final GeographicMetadata metadata;
101:
102:            /**
103:             * The parent of child {@linkplain Element elements}.
104:             */
105:            private final Node parent;
106:
107:            /**
108:             * The {@linkplain #childs} path. This is the {@code childPath} parameter
109:             * given to the constructor.
110:             */
111:            private final String childPath;
112:
113:            /**
114:             * The list of child elements. May be empty but never null.
115:             */
116:            private final List/*<Node>*/childs;
117:
118:            /**
119:             * The current element, or {@code null} if not yet selected.
120:             *
121:             * @see #selectChild
122:             * @see #currentElement()
123:             */
124:            private transient Element current;
125:
126:            /**
127:             * {@code true} if warnings are enabled.
128:             */
129:            private transient boolean warningsEnabled = true;
130:
131:            /**
132:             * Creates an accessor with the same parent and childs than the specified one. The two
133:             * accessors will share the same {@linkplain Node metadata nodes} (including the list
134:             * of childs), so change in one accessor will be immediately reflected in the other
135:             * accessor. However each accessor can {@linkplain #selectChild select their child}
136:             * independently.
137:             * <p>
138:             * The main purpose of this constructor is to create many views over the same list
139:             * of childs, where each view {@linkplain #selectChild select} a different child.
140:             */
141:            protected MetadataAccessor(final MetadataAccessor clone) {
142:                metadata = clone.metadata;
143:                parent = clone.parent;
144:                childPath = clone.childPath;
145:                childs = clone.childs;
146:            }
147:
148:            /**
149:             * Creates an accessor for the {@linkplain Element element} at the given path. Paths are
150:             * separated by the {@code '/'} character. See {@linkplain MetadataAccessor class javadoc}
151:             * for path examples.
152:             *
153:             * @param  metadata   The metadata node.
154:             * @param  parentPath The path to the {@linkplain Node node} of interest, or {@code null}
155:             *                    if the {@code metadata} root node is directly the node of interest.
156:             * @param  childPath  The path (relative to {@code parentPath}) to the child
157:             *                    {@linkplain Element elements}, or {@code null} if none.
158:             */
159:            protected MetadataAccessor(final GeographicMetadata metadata,
160:                    final String parentPath, final String childPath) {
161:                this .metadata = metadata;
162:                final Node root = metadata.getRootNode();
163:                /*
164:                 * Fetchs the parent node and ensure that we got a singleton. If there is more nodes than
165:                 * expected, log a warning and pickup the first one. If there is no node, create a new one.
166:                 */
167:                final List childs = new ArrayList(4);
168:                if (parentPath != null) {
169:                    listChilds(root, parentPath, 0, childs, true);
170:                    final int count = childs.size();
171:                    switch (count) {
172:                    default: {
173:                        warning("<init>", ErrorKeys.TOO_MANY_OCCURENCES_$2,
174:                                new Object[] { parentPath, new Integer(count) });
175:                        // Fall through for picking the first node.
176:                    }
177:                    case 1: {
178:                        parent = (Node) childs.get(0);
179:                        childs.clear();
180:                        break;
181:                    }
182:                    case 0: {
183:                        parent = appendChild(root, parentPath);
184:                        break;
185:                    }
186:                    }
187:                } else {
188:                    parent = root;
189:                }
190:                /*
191:                 * Computes a full path to children. Searching from 'metadata' root node using 'path'
192:                 * should be identical to searching from 'parent' node using 'childPath', except in
193:                 * case of badly formed metadata where the parent node appears more than once.
194:                 */
195:                this .childPath = childPath;
196:                if (childPath != null) {
197:                    final String path;
198:                    if (parentPath != null) {
199:                        path = parentPath + SEPARATOR + childPath;
200:                    } else {
201:                        path = childPath;
202:                    }
203:                    listChilds(root, path, 0, childs, false);
204:                    this .childs = childs;
205:                } else {
206:                    this .childs = Collections.EMPTY_LIST;
207:                }
208:                if (parent instanceof  Element) {
209:                    current = (Element) parent;
210:                }
211:            }
212:
213:            /**
214:             * Adds to the {@link #childs} list the child nodes at the given {@code path}.
215:             * This method is for constructor implementation only and invokes itself recursively.
216:             *
217:             * @param  parent The parent metadata node.
218:             * @param  path   The path to the nodes or elements to insert into the list.
219:             * @param  base   The offset in {@code path} for the next element name.
220:             * @param  childs The list where to insert the nodes or elements.
221:             * @param  includeNodes {@code true} of adding nodes as well as elements.
222:             */
223:            private static void listChilds(final Node parent,
224:                    final String path, final int base,
225:                    final List/*<Node>*/childs, final boolean includeNodes) {
226:                final int upper = path.indexOf(SEPARATOR, base);
227:                final String name = ((upper >= 0) ? path.substring(base, upper)
228:                        : path.substring(base)).trim();
229:                final NodeList list = parent.getChildNodes();
230:                final int length = list.getLength();
231:                for (int i = 0; i < length; i++) {
232:                    final Node candidate = list.item(i);
233:                    if (name.equals(candidate.getNodeName())) {
234:                        if (upper >= 0) {
235:                            listChilds(candidate, path, upper + 1, childs,
236:                                    includeNodes);
237:                        } else if (includeNodes
238:                                || (candidate instanceof  Element)) {
239:                            // For the very last node, we may require an element.
240:                            childs.add(candidate);
241:                        }
242:                    }
243:                }
244:            }
245:
246:            /**
247:             * Appends a child to the given parent.
248:             *
249:             * @param parent   The parent to add a child to.
250:             * @param path     The path of the child to add.
251:             * @return element The new child.
252:             */
253:            private static Node appendChild(Node parent, final String path) {
254:                int lower = 0;
255:                search: for (int upper; (upper = path.indexOf(SEPARATOR, lower)) >= 0; lower = upper + 1) {
256:                    final String name = path.substring(lower, upper).trim();
257:                    final NodeList list = parent.getChildNodes();
258:                    final int length = list.getLength();
259:                    for (int i = length; --i >= 0;) {
260:                        final Node candidate = list.item(i);
261:                        if (name.equals(candidate.getNodeName())) {
262:                            parent = candidate;
263:                            continue search;
264:                        }
265:                    }
266:                    parent = parent.appendChild(new IIOMetadataNode(name
267:                            .intern()));
268:                }
269:                final String name = path.substring(lower).trim().intern();
270:                return parent.appendChild(new IIOMetadataNode(name));
271:            }
272:
273:            /**
274:             * Returns the number of child {@linkplain Element elements}.
275:             * This is the upper value (exclusive) for {@link #selectChild}.
276:             *
277:             * @return The child {@linkplain Element elements} count.
278:             *
279:             * @see #selectChild
280:             * @see #appendChild
281:             */
282:            protected int childCount() {
283:                return childs.size();
284:            }
285:
286:            /**
287:             * Adds a new child {@linkplain Element element} at the path given at construction time.
288:             * The {@linkplain #childCount child count} will be increased by 1.
289:             * <p>
290:             * The new child is <strong>not</strong> automatically selected. In order to select this
291:             * new child, the {@link #selectChild} method must be invoked explicitly.
292:             *
293:             * @return The index of the new child element.
294:             *
295:             * @see #childCount
296:             * @see #selectChild
297:             */
298:            protected int appendChild() {
299:                final int size = childs.size();
300:                final Node child = appendChild(parent, childPath);
301:                if (child instanceof  Element) {
302:                    childs.add((Element) child);
303:                    return size;
304:                } else {
305:                    throw new UnsupportedImplementationException(child
306:                            .getClass());
307:                }
308:            }
309:
310:            /**
311:             * Selects the {@linkplain Element element} at the given index. Every subsequent calls
312:             * to {@code get} or {@code set} methods will apply to this selected child element.
313:             *
314:             * @param index The index of the element to select.
315:             * @throws IndexOutOfBoundsException if the specified index is out of bounds.
316:             *
317:             * @see #childCount
318:             * @see #appendChild
319:             * @see #selectParent
320:             */
321:            protected void selectChild(final int index)
322:                    throws IndexOutOfBoundsException {
323:                current = (Element) childs.get(index);
324:            }
325:
326:            /**
327:             * Selects the <em>parent</em> of child elements. Every subsequent calls to {@code get}
328:             * or {@code set} methods will apply to this parent element.
329:             *
330:             * @throws NoSuchElementException if there is no parent {@linkplain Element element}.
331:             *
332:             * @see #selectChild
333:             */
334:            protected void selectParent() throws NoSuchElementException {
335:                if (parent instanceof  Element) {
336:                    current = (Element) parent;
337:                } else {
338:                    throw new NoSuchElementException();
339:                }
340:            }
341:
342:            /**
343:             * Returns the current element.
344:             *
345:             * @return The currently selected element.
346:             * @throws IllegalStateException if there is no selected element.
347:             *
348:             * @see #selectChild
349:             */
350:            private Element currentElement() throws IllegalStateException {
351:                if (current == null) {
352:                    throw new IllegalStateException();
353:                }
354:                return current;
355:            }
356:
357:            /**
358:             * Returns the {@linkplain IIOMetadataNode#getUserObject user object} associated with the
359:             * {@linkplain #selectChild selected element}, or {@code null} if none. If no user object
360:             * is defined for the element, then the {@linkplain Node#getNodeValue node value} is returned
361:             * as a fallback. This is consistent with {@link #setUserObject} implementation, and allows
362:             * some parsing of nodes that are not {@link IIOMetadataNode} instances.
363:             * <p>
364:             * The {@code getUserObject} methods are the only ones to not parse the value returned by
365:             * {@link #getString}.
366:             *
367:             * @return The user object, or {@code null} if none.
368:             *
369:             * @see #getUserObject(Class)
370:             * @see #setUserObject
371:             */
372:            protected Object getUserObject() {
373:                final Element element = currentElement();
374:                if (element instanceof  IIOMetadataNode) {
375:                    final Object candidate = ((IIOMetadataNode) element)
376:                            .getUserObject();
377:                    if (candidate != null) {
378:                        return candidate;
379:                    }
380:                }
381:                /*
382:                 * getNodeValue() returns a String. We use it as a fallback, but in typical
383:                 * IIOMetadataNode usage this value is not used (according its javadoc), so
384:                 * it will often be null.
385:                 */
386:                return element.getNodeValue();
387:            }
388:
389:            /**
390:             * Returns the user object associated as an instance of the specified class. If the value
391:             * returned by {@link #getUserObject()} is not of the expected type, then this method will
392:             * tries to parse it as a string.
393:             *
394:             * @param  type The expected class.
395:             * @return The user object, or {@code null} if none.
396:             * @throws ClassCastException if the user object can not be casted to the specified type.
397:             *
398:             * @see #getUserObject()
399:             * @see #setUserObject
400:             */
401:            protected Object /*T*/getUserObject(Class/*<T>*/type)
402:                    throws ClassCastException {
403:                type = XMath.primitiveToWrapper(type);
404:                Object value = getUserObject();
405:                if (value instanceof  CharSequence) {
406:                    if (Number.class.isAssignableFrom(type)) {
407:                        value = XMath.valueOf(type, value.toString());
408:                    } else {
409:                        final Class component = XMath.primitiveToWrapper(type
410:                                .getComponentType());
411:                        if (Double.class.equals(component)) {
412:                            value = parseSequence(value.toString(), false,
413:                                    false);
414:                        } else if (Integer.class.equals(component)) {
415:                            value = parseSequence(value.toString(), false, true);
416:                        }
417:                    }
418:                }
419:                return value; // TODO: use type.cast with Java 5.
420:            }
421:
422:            /**
423:             * Sets the {@linkplain IIOMetadataNode#setUserObject user object} associated with the
424:             * {@linkplain #selectChild selected element}. This is the only {@code set} method that
425:             * doesn't invoke {@link #setString} with a formatted value.
426:             * <p>
427:             * If the specified value is formattable (i.e. is a {@linkplain CharSequence character
428:             * sequence}, a {@linkplain Number number} or an array of the above), then this method
429:             * also {@linkplain IIOMetadataNode#setNodeValue sets the node value} as a string. This
430:             * is mostly a convenience for formatting purpose since {@link IIOMetadataNode} don't
431:             * use the node value. But it may help some libraries that are not designed to work with
432:             * with user objects, since they are particular to Image I/O metadata.
433:             *
434:             * @param  value The user object, or {@code null} if none.
435:             * @throws UnsupportedImplementationException if the selected element is not an instance of
436:             *         {@link IIOMetadataNode}.
437:             *
438:             * @see #getUserObject()
439:             */
440:            protected void setUserObject(final Object value)
441:                    throws UnsupportedImplementationException {
442:                final Element element = currentElement();
443:                final String asText;
444:                if (isFormattable(value)) {
445:                    asText = value.toString();
446:                } else if (value != null
447:                        && isFormattable(value.getClass().getComponentType())) {
448:                    asText = formatSequence(value);
449:                } else {
450:                    asText = null;
451:                }
452:                if (element instanceof  IIOMetadataNode) {
453:                    ((IIOMetadataNode) element).setUserObject(value);
454:                } else if (value != null && asText == null) {
455:                    throw new UnsupportedImplementationException(Errors.format(
456:                            ErrorKeys.ILLEGAL_CLASS_$2, Utilities
457:                                    .getShortClassName(element), Utilities
458:                                    .getShortName(IIOMetadataNode.class)));
459:                }
460:                element.setNodeValue(asText);
461:            }
462:
463:            /**
464:             * Returns {@code true} if the specified value can be formatted as a text.
465:             * We allows formatting only for reasonably cheap objects, for example a
466:             * Number but not a CoordinateReferenceSystem.
467:             */
468:            private static boolean isFormattable(final Object value) {
469:                return (value instanceof  CharSequence)
470:                        || (value instanceof  Number);
471:            }
472:
473:            /**
474:             * Returns an attribute as a string for the {@linkplain #selectChild selected element},
475:             * or {@code null} if none. This method never returns an empty string.
476:             * <p>
477:             * Every {@code get} methods in this class except {@link #getUserObject getUserObject}
478:             * invoke this method first. Consequently, this method provides a single point for
479:             * overriding if subclasses want to process the attribute before parsing.
480:             *
481:             * @param attribute The attribute to fetch (e.g. {@code "name"}).
482:             * @return The attribute value (never an empty string), or {@code null} if none.
483:             */
484:            protected String getString(final String attribute) {
485:                String candidate = currentElement().getAttribute(attribute);
486:                if (candidate != null) {
487:                    candidate = candidate.trim();
488:                    if (candidate.length() == 0) {
489:                        candidate = null;
490:                    }
491:                }
492:                return candidate;
493:            }
494:
495:            /**
496:             * Set the attribute to the specified value,
497:             * or remove the attribute if the value is null.
498:             * <p>
499:             * Every {@code set} methods in this class except {@link #setUserObject setUserObject}
500:             * invoke this method last. Consequently, this method provides a single point for
501:             * overriding if subclasses want to process the attribute after formatting.
502:             *
503:             * @param attribute The attribute name.
504:             * @param value     The attribute value.
505:             */
506:            protected void setString(final String attribute, String value) {
507:                final Element element = currentElement();
508:                if (value == null || (value = value.trim()).length() == 0) {
509:                    if (element.hasAttribute(attribute)) {
510:                        element.removeAttribute(attribute);
511:                    }
512:                } else {
513:                    element.setAttribute(attribute, value);
514:                }
515:            }
516:
517:            /**
518:             * Set the attribute to the specified enumeration value,
519:             * or remove the attribute if the value is null.
520:             *
521:             * @param attribute The attribute name.
522:             * @param value     The attribute value.
523:             * @param enums     The set of allowed values, or {@code null} if unknown.
524:             */
525:            final void setEnum(final String attribute, String value,
526:                    final Collection enums) {
527:                if (value != null) {
528:                    value = value.replace('_', ' ').trim();
529:                    for (final Iterator it = enums.iterator(); it.hasNext();) {
530:                        final String e = (String) it.next();
531:                        if (value.equalsIgnoreCase(e)) {
532:                            value = e;
533:                            break;
534:                        }
535:                    }
536:                }
537:                setString(attribute, value);
538:            }
539:
540:            /**
541:             * Returns an attribute as an integer for the {@linkplain #selectChild selected element},
542:             * or {@code null} if none. If the attribute can't be parsed as an integer, then this method
543:             * logs a warning and returns {@code null}.
544:             *
545:             * @param attribute The attribute to fetch (e.g. {@code "minimum"}).
546:             * @return The attribute value, or {@code null} if none or unparseable.
547:             */
548:            protected Integer getInteger(final String attribute) {
549:                String value = getString(attribute);
550:                if (value != null) {
551:                    value = trimFractionalPart(value);
552:                    try {
553:                        return Integer.valueOf(value);
554:                    } catch (NumberFormatException e) {
555:                        warning("getInteger", ErrorKeys.UNPARSABLE_NUMBER_$1,
556:                                value);
557:                    }
558:                }
559:                return null;
560:            }
561:
562:            /**
563:             * Set the attribute to the specified integer value.
564:             *
565:             * @param attribute The attribute name.
566:             * @param value     The attribute value.
567:             */
568:            protected void setInteger(final String attribute, final int value) {
569:                setString(attribute, Integer.toString(value));
570:            }
571:
572:            /**
573:             * Returns an attribute as an array of integers for the {@linkplain #selectChild selected
574:             * element}, or {@code null} if none. If an element can't be parsed as an integer, then this
575:             * method logs a warning and returns {@code null}.
576:             *
577:             * @param attribute The attribute to fetch (e.g. {@code "minimum"}).
578:             * @param unique {@code true} if duplicated values should be collapsed into unique values,
579:             *         or {@code false} for preserving duplicated values.
580:             * @return The attribute values, or {@code null} if none.
581:             */
582:            protected int[] getIntegers(final String attribute,
583:                    final boolean unique) {
584:                return (int[]) parseSequence(getString(attribute), unique, true);
585:            }
586:
587:            /**
588:             * Set the attribute to the specified array of values,
589:             * or remove the attribute if the array is {@code null}.
590:             *
591:             * @param attribute The attribute name.
592:             * @param value     The attribute value.
593:             */
594:            protected void setIntegers(final String attribute,
595:                    final int[] values) {
596:                setString(attribute, formatSequence(values));
597:            }
598:
599:            /**
600:             * Returns an attribute as a floating point for the {@linkplain #selectChild selected element},
601:             * or {@code null} if none. If the attribute can't be parsed as a floating point, then this
602:             * method logs a warning and returns {@code null}.
603:             *
604:             * @param attribute The attribute to fetch (e.g. {@code "minimum"}).
605:             * @return The attribute value, or {@code null} if none or unparseable.
606:             */
607:            protected Double getDouble(final String attribute) {
608:                final String value = getString(attribute);
609:                if (value != null)
610:                    try {
611:                        return Double.valueOf(value);
612:                    } catch (NumberFormatException e) {
613:                        warning("getDouble", ErrorKeys.UNPARSABLE_NUMBER_$1,
614:                                value);
615:                    }
616:                return null;
617:            }
618:
619:            /**
620:             * Set the attribute to the specified floating point value,
621:             * or remove the attribute if the value is NaN.
622:             *
623:             * @param attribute The attribute name.
624:             * @param value     The attribute value.
625:             */
626:            protected void setDouble(final String attribute, final double value) {
627:                String text = null;
628:                if (!Double.isNaN(value) && !Double.isInfinite(value)) {
629:                    text = Double.toString(value);
630:                }
631:                setString(attribute, text);
632:            }
633:
634:            /**
635:             * Returns an attribute as an array of floating point for the {@linkplain #selectChild
636:             * selected element}, or {@code null} if none. If an element can't be parsed as a floating
637:             * point, then this method logs a warning and returns {@code null}.
638:             *
639:             * @param  attribute The attribute to fetch (e.g. {@code "fillValues"}).
640:             * @param  unique {@code true} if duplicated values should be collapsed into unique values,
641:             *         or {@code false} for preserving duplicated values.
642:             * @return The attribute values, or {@code null} if none.
643:             */
644:            protected double[] getDoubles(final String attribute,
645:                    final boolean unique) {
646:                return (double[]) parseSequence(getString(attribute), unique,
647:                        false);
648:            }
649:
650:            /**
651:             * Set the attribute to the specified array of values,
652:             * or remove the attribute if the array is {@code null}.
653:             *
654:             * @param attribute The attribute name.
655:             * @param value     The attribute value.
656:             */
657:            protected void setDoubles(final String attribute,
658:                    final double[] values) {
659:                setString(attribute, formatSequence(values));
660:            }
661:
662:            /**
663:             * Implementation of {@link #getIntegers} and {@link #getDoubles} methods.
664:             *
665:             * @param  sequence The sequence to parse.
666:             * @param  unique {@code true} if duplicated values should be collapsed into unique values,
667:             *         or {@code false} for preserving duplicated values.
668:             * @param  integers {@code true} for parsing as {@code int}, or {@code false} for parsing as
669:             *         {@code double}.
670:             * @return The attribute values, or {@code null} if none.
671:             */
672:            private Object parseSequence(final String sequence,
673:                    final boolean unique, final boolean integers) {
674:                if (sequence == null) {
675:                    return null;
676:                }
677:                final Collection/*<Number>*/numbers;
678:                if (unique) {
679:                    numbers = new LinkedHashSet();
680:                } else {
681:                    numbers = new ArrayList();
682:                }
683:                final StringTokenizer tokens = new StringTokenizer(sequence);
684:                while (tokens.hasMoreTokens()) {
685:                    final String token = tokens.nextToken();
686:                    final Number number;
687:                    try {
688:                        if (integers) {
689:                            number = Integer.valueOf(token);
690:                        } else {
691:                            number = Double.valueOf(token);
692:                        }
693:                    } catch (NumberFormatException e) {
694:                        warning(integers ? "getIntegers" : "getDoubles",
695:                                ErrorKeys.UNPARSABLE_NUMBER_$1, token);
696:                        continue;
697:                    }
698:                    numbers.add(number);
699:                }
700:                int count = 0;
701:                final Object values;
702:                if (integers) {
703:                    values = new int[numbers.size()];
704:                } else {
705:                    values = new double[numbers.size()];
706:                }
707:                for (final Iterator it = numbers.iterator(); it.hasNext();) {
708:                    Array.set(values, count++, it.next());
709:                }
710:                assert Array.getLength(values) == count;
711:                return values;
712:            }
713:
714:            /**
715:             * Formats a sequence for {@link #setIntegers} and {@link #setDoubles} implementations.
716:             *
717:             * @param  value The attribute value.
718:             * @return The formatted sequence.
719:             */
720:            private static String formatSequence(final Object values) {
721:                String text = null;
722:                if (values != null) {
723:                    final StringBuffer buffer = new StringBuffer();
724:                    final int length = Array.getLength(values);
725:                    for (int i = 0; i < length; i++) {
726:                        if (i != 0) {
727:                            buffer.append(' ');
728:                        }
729:                        buffer.append(Array.get(values, i));
730:                    }
731:                    text = buffer.toString();
732:                }
733:                return text;
734:            }
735:
736:            /**
737:             * Returns an attribute as a date for the {@linkplain #selectChild selected element},
738:             * or {@code null} if none. If the attribute can't be parsed as a date, then this method
739:             * logs a warning and returns {@code null}.
740:             *
741:             * @param attribute The attribute to fetch (e.g. {@code "origin"}).
742:             * @return The attribute value, or {@code null} if none or unparseable.
743:             */
744:            protected Date getDate(final String attribute) {
745:                String value = getString(attribute);
746:                if (value != null) {
747:                    value = trimFractionalPart(value);
748:                    // TODO: remove the cast with J2SE 1.5.
749:                    return (Date) metadata.dateFormat().parse(value);
750:                }
751:                return null;
752:            }
753:
754:            /**
755:             * Set the attribute to the specified value, or remove the attribute if the value is null.
756:             *
757:             * @param attribute The attribute name.
758:             * @param value     The attribute value.
759:             */
760:            protected void setDate(final String attribute, final Date value) {
761:                String text = null;
762:                if (value != null) {
763:                    text = metadata.dateFormat().format(value);
764:                }
765:                setString(attribute, text);
766:            }
767:
768:            /**
769:             * Trims the factional part of the given string, provided that it doesn't change the value.
770:             * More specifically, this method removes the trailing {@code ".0"} characters if any. This
771:             * method is automatically invoked before to {@linkplain #getInteger parse an integer} or to
772:             * {@linkplain #getDate parse a date} (for simplifying fractional seconds).
773:             *
774:             * @param  value The value to trim.
775:             * @return The value without the trailing {@code ".0"} part.
776:             */
777:            public static String trimFractionalPart(String value) {
778:                value = value.trim();
779:                for (int i = value.length(); --i >= 0;) {
780:                    switch (value.charAt(i)) {
781:                    case '0':
782:                        continue;
783:                    case '.':
784:                        return value.substring(0, i);
785:                    default:
786:                        return value;
787:                    }
788:                }
789:                return value;
790:            }
791:
792:            /**
793:             * Convenience method for logging a warning. Do not allow overriding, because
794:             * it would not work for warnings emitted by the {@link #getDate} method.
795:             */
796:            final void warning(final String method, final int key,
797:                    final Object value) {
798:                if (warningsEnabled) {
799:                    final LogRecord record = Errors.getResources(
800:                            metadata.getLocale()).getLogRecord(Level.WARNING,
801:                            key, value);
802:                    record.setSourceClassName(MetadataAccessor.class.getName());
803:                    record.setSourceMethodName(method);
804:                    warningOccurred(record);
805:                }
806:            }
807:
808:            /**
809:             * Invoked when a warning occured. This method is invoked when some inconsistency has
810:             * been detected in the geographic metadata. The default implementation delegates
811:             * to {@link GeographicMetadata#warningOccurred}.
812:             */
813:            protected void warningOccurred(final LogRecord record) {
814:                if (warningsEnabled) {
815:                    metadata.warningOccurred(record);
816:                }
817:            }
818:
819:            /**
820:             * Enables or disables the warnings. Warnings are enabled by default. Subclasses way want
821:             * to temporarily disable the warnings when failures are expected as the normal behavior.
822:             * For example a subclass may invokes {@link #getInteger} and fallbacks on {@link #getDouble}
823:             * if the former failed. In such case, the warnings should be disabled for the integer parsing,
824:             * but not for the floating point parsing.
825:             *
826:             * @param  enabled {@code true} for enabling warnings, or {@code false} for disabling.
827:             * @return The previous state before this method has been invoked.
828:             */
829:            protected boolean setWarningsEnabled(final boolean enabled) {
830:                final boolean old = warningsEnabled;
831:                warningsEnabled = enabled;
832:                return old;
833:            }
834:
835:            /**
836:             * Returns a string representation of metadata, mostly for debugging purpose.
837:             */
838:            public String toString() {
839:                return OptionalDependencies.toString(OptionalDependencies
840:                        .xmlToSwing(parent));
841:            }
842:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.