Coverage Report - org.geotoolkit.naming.DefaultScopedName
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultScopedName
73 %
67/91
58 %
24/41
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  
 }