| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| DefaultScopedName |
|
| 4.181818181818182;4,182 |
| 1 | /* | |
| 2 | * Geotoolkit.org - An Open Source Java GIS Toolkit | |
| 3 | * http://www.geotoolkit.org | |
| 4 | * | |
| 5 | * (C) 2004-2012, Open Source Geospatial Foundation (OSGeo) | |
| 6 | * (C) 2009-2012, Geomatys | |
| 7 | * | |
| 8 | * This library is free software; you can redistribute it and/or | |
| 9 | * modify it under the terms of the GNU Lesser General Public | |
| 10 | * License as published by the Free Software Foundation; | |
| 11 | * version 2.1 of the License. | |
| 12 | * | |
| 13 | * This library is distributed in the hope that it will be useful, | |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 16 | * Lesser General Public License for more details. | |
| 17 | * | |
| 18 | * This package contains documentation from OpenGIS specifications. | |
| 19 | * OpenGIS consortium's work is fully acknowledged here. | |
| 20 | */ | |
| 21 | package org.geotoolkit.naming; | |
| 22 | ||
| 23 | import java.util.List; | |
| 24 | import java.util.Iterator; | |
| 25 | import javax.xml.bind.annotation.XmlElement; | |
| 26 | import javax.xml.bind.annotation.XmlRootElement; | |
| 27 | import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |
| 28 | import net.jcip.annotations.Immutable; | |
| 29 | ||
| 30 | import org.opengis.util.NameSpace; | |
| 31 | import org.opengis.util.LocalName; | |
| 32 | import org.opengis.util.ScopedName; | |
| 33 | import org.opengis.util.GenericName; | |
| 34 | import org.opengis.util.InternationalString; | |
| 35 | ||
| 36 | import org.geotoolkit.resources.Errors; | |
| 37 | import org.geotoolkit.internal.jaxb.gco.LocalNameAdapter; | |
| 38 | import org.geotoolkit.util.collection.UnmodifiableArrayList; | |
| 39 | ||
| 40 | import static org.apache.sis.util.ArgumentChecks.ensureNonNull; | |
| 41 | ||
| 42 | ||
| 43 | /** | |
| 44 | * A composite of a {@linkplain NameSpace name space} (as a {@linkplain LocalName local name}) | |
| 45 | * and a {@linkplain GenericName generic name} valid in that name space. See the | |
| 46 | * {@linkplain ScopedName GeoAPI javadoc} for more information. | |
| 47 | * <p> | |
| 48 | * {@code DefaultScopedName} can be instantiated by any of the following methods: | |
| 49 | * <ul> | |
| 50 | * <li>{@link DefaultNameFactory#createGenericName(NameSpace, CharSequence[])} with an array of length 2 or more</li> | |
| 51 | * <li>{@link DefaultNameFactory#parseGenericName(NameSpace, CharSequence)} with at least one separator</li> | |
| 52 | * </ul> | |
| 53 | * | |
| 54 | * @author Martin Desruisseaux (Geomatys) | |
| 55 | * @version 3.00 | |
| 56 | * | |
| 57 | * @since 2.1 | |
| 58 | * @module | |
| 59 | */ | |
| 60 | @Immutable | |
| 61 | @XmlRootElement(name = "ScopedName") | |
| 62 | public class DefaultScopedName extends AbstractName implements ScopedName { | |
| 63 | /** | |
| 64 | * Serial number for inter-operability with different versions. | |
| 65 | */ | |
| 66 | private static final long serialVersionUID = -5215955533541748481L; | |
| 67 | ||
| 68 | /** | |
| 69 | * The immutable list of parsed names. | |
| 70 | */ | |
| 71 | private final UnmodifiableArrayList<? extends LocalName> parsedNames; | |
| 72 | ||
| 73 | /** | |
| 74 | * The tail or path, computed when first needed. | |
| 75 | */ | |
| 76 | private transient GenericName tail, path; | |
| 77 | ||
| 78 | /** | |
| 79 | * Creates a new scoped names from the given list of local names. This constructor is | |
| 80 | * not public because we do not check if the given local names have the proper scope. | |
| 81 | * | |
| 82 | * @param names The names to gives to the new scoped name. | |
| 83 | */ | |
| 84 | static AbstractName create(final UnmodifiableArrayList<? extends DefaultLocalName> names) { | |
| 85 | 22 | ensureNonNull("names", names); |
| 86 | 22 | switch (names.size()) { |
| 87 | 15 | default: return new DefaultScopedName(names); |
| 88 | 7 | case 1: return names.get(0); |
| 89 | 0 | case 0: throw new IllegalArgumentException(Errors.format(Errors.Keys.EMPTY_ARRAY)); |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Empty constructor to be used by JAXB only. Despite its "final" declaration, | |
| 95 | * the {@link #parsedNames} field will be set by JAXB during unmarshalling. | |
| 96 | */ | |
| 97 | 0 | private DefaultScopedName() { |
| 98 | 0 | parsedNames = null; |
| 99 | 0 | } |
| 100 | ||
| 101 | /** | |
| 102 | * Creates a new scoped names from the given list of local names. This constructor is | |
| 103 | * not public because it does not check if the given local names have the proper scope. | |
| 104 | * | |
| 105 | * @param names The names to gives to the new scoped name. | |
| 106 | */ | |
| 107 | 46 | private DefaultScopedName(final UnmodifiableArrayList<? extends LocalName> names) { |
| 108 | 46 | parsedNames = names; |
| 109 | 46 | } |
| 110 | ||
| 111 | /** | |
| 112 | * Constructs a scoped name from the specified list of strings. If any of the given names is an | |
| 113 | * instance of {@link InternationalString}, then its {@link InternationalString#toString(java.util.Locale) | |
| 114 | * toString(null)} method will be invoked for fetching an unlocalized name. Otherwise the | |
| 115 | * {@link CharSequence#toString toString()} method will be used. | |
| 116 | * | |
| 117 | * @param scope The scope of this name, or {@code null} for the global scope. | |
| 118 | * @param names The local names. This list must have at least two elements. | |
| 119 | */ | |
| 120 | 7 | protected DefaultScopedName(final NameSpace scope, final List<? extends CharSequence> names) { |
| 121 | 7 | ensureNonNull("names", names); |
| 122 | 7 | final int size = names.size(); |
| 123 | 7 | if (size < 2) { |
| 124 | 0 | throw new IllegalArgumentException(Errors.format( |
| 125 | Errors.Keys.ILLEGAL_ARGUMENT_2, "size", size)); | |
| 126 | } | |
| 127 | 7 | DefaultNameSpace ns = DefaultNameSpace.wrap(scope); |
| 128 | 7 | final boolean global = ns.isGlobal(); |
| 129 | 7 | int i = 0; |
| 130 | 7 | final LocalName[] locals = new LocalName[size]; |
| 131 | 7 | final Iterator<? extends CharSequence> it = names.iterator(); |
| 132 | /* | |
| 133 | * Builds the parsed name list by creating DefaultLocalName instances now. | |
| 134 | * Note that we expect at least 2 valid entries (because of the check we | |
| 135 | * did before), so we don't check hasNext() for the two first entries. | |
| 136 | */ | |
| 137 | 7 | CharSequence name = it.next(); |
| 138 | do { | |
| 139 | 21 | ensureNonNull("name", name); |
| 140 | 21 | locals[i++] = new DefaultLocalName(ns, name); |
| 141 | 21 | ns = ns.child(name); |
| 142 | 21 | name = it.next(); |
| 143 | 21 | } while (it.hasNext()); |
| 144 | /* | |
| 145 | * At this point, we have almost finished to build the parsed names array. | |
| 146 | * The last name is the tip, which we want to live in the given namespace. | |
| 147 | * If this namespace is global, then the fully qualified name is this name. | |
| 148 | * In this case we assign the reference now in order to avoid letting | |
| 149 | * tip.toFullyQualifiedName() creates a new object later. | |
| 150 | */ | |
| 151 | 7 | final DefaultLocalName tip = ns.local(name, null); |
| 152 | 7 | if (global) { |
| 153 | 4 | tip.fullyQualified = fullyQualified = this; |
| 154 | } | |
| 155 | 7 | locals[i++] = tip; |
| 156 | 7 | if (i != size) { |
| 157 | 0 | throw new AssertionError(); // Paranoiac check. |
| 158 | } | |
| 159 | 7 | parsedNames = UnmodifiableArrayList.wrap(locals); |
| 160 | 7 | } |
| 161 | ||
| 162 | /** | |
| 163 | * Constructs a scoped name as the concatenation of the given generic names. | |
| 164 | * The scope of the new name will be the scope of the {@code path} argument. | |
| 165 | * | |
| 166 | * @param path The first part to concatenate. | |
| 167 | * @param tail The second part to concatenate. | |
| 168 | */ | |
| 169 | 19 | protected DefaultScopedName(final GenericName path, final GenericName tail) { |
| 170 | 19 | ensureNonNull("path", path); |
| 171 | 19 | ensureNonNull("tail", tail); |
| 172 | 19 | final List<? extends LocalName> parsedPath = path.getParsedNames(); |
| 173 | 19 | final List<? extends LocalName> parsedTail = tail.getParsedNames(); |
| 174 | 19 | int index = parsedPath.size(); |
| 175 | 19 | LocalName[] locals = new LocalName[index + parsedTail.size()]; |
| 176 | 19 | locals = parsedPath.toArray(locals); |
| 177 | /* | |
| 178 | * We have copied the LocalNames from the path unconditionally. Now we need to process the | |
| 179 | * LocalNames from the tail. If the tail scope follow the path scope, we can just copy the | |
| 180 | * names without further processing (easy case). Otherwise we need to create new instances. | |
| 181 | * | |
| 182 | * Note that by contract, GenericName must contains at least 1 element. This assumption | |
| 183 | * appears in two places: it.next() invoked once before any it.hasNext(), and testing for | |
| 184 | * locals[index-1] element (so we assume index > 0). | |
| 185 | */ | |
| 186 | 19 | final Iterator<? extends LocalName> it = parsedTail.iterator(); |
| 187 | 19 | LocalName name = it.next(); |
| 188 | 19 | final LocalName lastName = locals[index-1]; |
| 189 | 19 | final NameSpace lastScope = lastName.scope(); |
| 190 | 19 | final NameSpace tailScope = name.scope(); |
| 191 | 19 | if (tailScope instanceof DefaultNameSpace && |
| 192 | ((DefaultNameSpace) tailScope).parent == lastScope) | |
| 193 | { | |
| 194 | /* | |
| 195 | * If the tail is actually the tip (a LocalName), remember the tail so we | |
| 196 | * don't need to create it again later. Then copy the tail after the path. | |
| 197 | */ | |
| 198 | 19 | if (path instanceof LocalName) { |
| 199 | 7 | this.tail = tail; |
| 200 | } | |
| 201 | while (true) { | |
| 202 | 27 | locals[index++] = name; |
| 203 | 27 | if (!it.hasNext()) break; |
| 204 | 8 | name = it.next(); |
| 205 | } | |
| 206 | } else { | |
| 207 | /* | |
| 208 | * There is no continuity in the chain of scopes, so we need to create new | |
| 209 | * LocalName instances. | |
| 210 | */ | |
| 211 | 0 | DefaultNameSpace scope = DefaultNameSpace.wrap(lastScope); |
| 212 | 0 | CharSequence label = name(lastName); |
| 213 | while (true) { | |
| 214 | 0 | scope = scope.child(label); |
| 215 | 0 | label = name(name); |
| 216 | 0 | name = new DefaultLocalName(scope, label); |
| 217 | 0 | locals[index++] = name; |
| 218 | 0 | if (!it.hasNext()) break; |
| 219 | 0 | name = it.next(); |
| 220 | } | |
| 221 | } | |
| 222 | 19 | if (index != locals.length) { |
| 223 | 0 | throw new AssertionError(); // Paranoiac check. |
| 224 | } | |
| 225 | 19 | parsedNames = UnmodifiableArrayList.wrap(locals); |
| 226 | 19 | if (tail instanceof LocalName) { |
| 227 | 16 | this.path = path; |
| 228 | } | |
| 229 | 19 | } |
| 230 | ||
| 231 | /** | |
| 232 | * Returns the name to be given to {@link DefaultLocalName} constructors. | |
| 233 | */ | |
| 234 | private static CharSequence name(final GenericName name) { | |
| 235 | 0 | if (name instanceof DefaultLocalName) { |
| 236 | 0 | return ((DefaultLocalName) name).name; |
| 237 | } | |
| 238 | 0 | final InternationalString label = name.toInternationalString(); |
| 239 | 0 | if (label != null) { |
| 240 | 0 | return label; |
| 241 | } | |
| 242 | 0 | return name.toString(); |
| 243 | } | |
| 244 | ||
| 245 | /** | |
| 246 | * Returns the size of the backing array. This is used only has a hint for optimizations | |
| 247 | * in attempts to share internal arrays. | |
| 248 | */ | |
| 249 | @Override | |
| 250 | final int arraySize() { | |
| 251 | 21 | return parsedNames.arraySize(); |
| 252 | } | |
| 253 | ||
| 254 | /** | |
| 255 | * {@inheritDoc} | |
| 256 | */ | |
| 257 | @Override | |
| 258 | public NameSpace scope() { | |
| 259 | 102 | return head().scope(); |
| 260 | } | |
| 261 | ||
| 262 | /** | |
| 263 | * Returns every elements of the {@linkplain #getParsedNames parsed names list} | |
| 264 | * except for the {@linkplain #head head}. | |
| 265 | */ | |
| 266 | @Override | |
| 267 | public synchronized GenericName tail() { | |
| 268 | 6 | if (tail == null) { |
| 269 | 3 | final int size = parsedNames.size(); |
| 270 | 3 | switch (size) { |
| 271 | 3 | default: tail = new DefaultScopedName(parsedNames.subList(1, size)); break; |
| 272 | 0 | case 2: tail = parsedNames.get(1); break; |
| 273 | case 1: // fall through | |
| 274 | 0 | case 0: throw new AssertionError(size); |
| 275 | } | |
| 276 | } | |
| 277 | 6 | return tail; |
| 278 | } | |
| 279 | ||
| 280 | /** | |
| 281 | * Returns every element of the {@linkplain #getParsedNames parsed names list} | |
| 282 | * except for the {@linkplain #tip tip}. | |
| 283 | */ | |
| 284 | @Override | |
| 285 | public synchronized GenericName path() { | |
| 286 | 86 | if (path == null) { |
| 287 | 43 | final int size = parsedNames.size(); |
| 288 | 43 | switch (size) { |
| 289 | 28 | default: path = new DefaultScopedName(parsedNames.subList(0, size-1)); break; |
| 290 | 15 | case 2: path = parsedNames.get(0); break; |
| 291 | case 1: // fall through | |
| 292 | 0 | case 0: throw new AssertionError(size); |
| 293 | } | |
| 294 | } | |
| 295 | 86 | return path; |
| 296 | } | |
| 297 | ||
| 298 | /** | |
| 299 | * Returns the sequence of local name for this {@linkplain GenericName generic name}. | |
| 300 | */ | |
| 301 | @Override | |
| 302 | @XmlJavaTypeAdapter(LocalNameAdapter.class) // Seems required in order to avoid random failures. | |
| 303 | @XmlElement(name = "parsedName", required = true) | |
| 304 | public List<? extends LocalName> getParsedNames() { | |
| 305 | 450 | return parsedNames; |
| 306 | } | |
| 307 | } |