Coverage Report - com.jcabi.xml.ListWrapper
 
Classes in this File Line Coverage Branch Coverage Complexity
ListWrapper
30%
11/36
5%
2/34
1.704
ListWrapper$NodeNotFoundException
100%
10/10
83%
5/6
1.704
 
 1  
 /**
 2  
  * Copyright (c) 2012-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.xml;
 31  
 
 32  
 import com.jcabi.log.Logger;
 33  
 import java.util.Collection;
 34  
 import java.util.Iterator;
 35  
 import java.util.List;
 36  
 import java.util.ListIterator;
 37  
 import lombok.EqualsAndHashCode;
 38  
 import org.w3c.dom.Node;
 39  
 
 40  
 /**
 41  
  * Wrapper of {@link List}.
 42  
  *
 43  
  * <p>This wrapper is our internal implementation of a {@link List}. The only
 44  
  * purpose of this wrapper is to throw our own custom exception when the client
 45  
  * is trying to access an element that is absent in the list. Such a custom
 46  
  * exception ({@link ListWrapper.NodeNotFoundException})
 47  
  * includes detailed information about
 48  
  * the original document. Thus, such an incorrect list-access operation will
 49  
  * lead to an exception that contains all the details inside (not just a simple
 50  
  * error message). For example:
 51  
  *
 52  
  * <pre> String name = new XMLDocument("...")
 53  
  *   .xpath("/document/name/text()")
 54  
  *   .get(0)</pre>
 55  
  *
 56  
  * <p>This snippet is trying to get a text value from XML element, and there
 57  
  * is no guarantee that such an element exists in the document. In order to give
 58  
  * a detailed report to the user of the problem (including a full content of
 59  
  * the XML document) we're returning {@link ListWrapper} from
 60  
  * {@link XMLDocument#xpath(String)}. The only method that we implement
 61  
  * for this purpose is {@link #get(int)}.
 62  
  *
 63  
  * <p>{@link ListWrapper} is an unmodifiable list, that's why
 64  
  * the majority of inherited method are not implemented and
 65  
  * thow runtime exceptions if being called.
 66  
  *
 67  
  * <p>The class is immutable and thread-safe.
 68  
  *
 69  
  * @author Yegor Bugayenko (yegor@teamed.io)
 70  
  * @version $Id: b79c6583429e7833cb26dab2ef34f9e5e6328db8 $
 71  
  * @since 0.1
 72  
  * @param <T> Time of items
 73  
  */
 74  0
 @EqualsAndHashCode(of = { "original", "dom", "xpath" })
 75  
 @SuppressWarnings("PMD.TooManyMethods")
 76  
 final class ListWrapper<T> implements List<T> {
 77  
 
 78  
     /**
 79  
      * The original list.
 80  
      */
 81  
     private final transient List<T> original;
 82  
 
 83  
     /**
 84  
      * The XML where this list came from.
 85  
      */
 86  
     private final transient Node dom;
 87  
 
 88  
     /**
 89  
      * XPath.
 90  
      */
 91  
     private final transient String xpath;
 92  
 
 93  
     /**
 94  
      * Public ctor.
 95  
      * @param list Original list
 96  
      * @param node The XML
 97  
      * @param addr Address
 98  
      */
 99  378
     ListWrapper(final List<T> list, final Node node, final String addr) {
 100  378
         this.original = list;
 101  378
         this.dom = node;
 102  378
         this.xpath = addr;
 103  378
     }
 104  
 
 105  
     @Override
 106  
     public String toString() {
 107  0
         return this.original.toString();
 108  
     }
 109  
 
 110  
     @Override
 111  
     public boolean add(final T element) {
 112  0
         throw new UnsupportedOperationException("#add(T)");
 113  
     }
 114  
 
 115  
     @Override
 116  
     public void add(final int index, final T element) {
 117  0
         throw new UnsupportedOperationException("#add(int, T)");
 118  
     }
 119  
 
 120  
     @Override
 121  
     public boolean addAll(final Collection<? extends T> elements) {
 122  0
         throw new UnsupportedOperationException("#addAll(Collection)");
 123  
     }
 124  
 
 125  
     @Override
 126  
     public boolean addAll(final int index, final Collection<? extends T> elms) {
 127  0
         throw new UnsupportedOperationException("#add(int, Collection)");
 128  
     }
 129  
 
 130  
     @Override
 131  
     public void clear() {
 132  0
         throw new UnsupportedOperationException("#clear()");
 133  
     }
 134  
 
 135  
     @Override
 136  
     public boolean contains(final Object element) {
 137  0
         return this.original.contains(element);
 138  
     }
 139  
 
 140  
     @Override
 141  
     public boolean containsAll(final Collection<?> elements) {
 142  0
         return this.original.containsAll(elements);
 143  
     }
 144  
 
 145  
     /**
 146  
      * {@inheritDoc}
 147  
      *
 148  
      * <p>The method throws {@link ListWrapper.NodeNotFoundException}
 149  
      * if such an element doesn't exist in the list.
 150  
      */
 151  
     @Override
 152  
     public T get(final int index) {
 153  112
         if (index >= this.size()) {
 154  1
             throw new ListWrapper.NodeNotFoundException(
 155  
                 String.format(
 156  
                     "Index (%d) is out of bounds (size=%d)",
 157  
                     index, this.size()
 158  
                 ),
 159  
                 this.dom,
 160  
                 this.xpath
 161  
             );
 162  
         }
 163  111
         return this.original.get(index);
 164  
     }
 165  
 
 166  
     @Override
 167  
     public int indexOf(final Object element) {
 168  0
         return this.original.indexOf(element);
 169  
     }
 170  
 
 171  
     @Override
 172  
     public boolean isEmpty() {
 173  209
         return this.original.isEmpty();
 174  
     }
 175  
 
 176  
     @Override
 177  
     public Iterator<T> iterator() {
 178  52
         return this.original.iterator();
 179  
     }
 180  
 
 181  
     @Override
 182  
     public int lastIndexOf(final Object element) {
 183  0
         return this.original.lastIndexOf(element);
 184  
     }
 185  
 
 186  
     @Override
 187  
     public ListIterator<T> listIterator() {
 188  0
         return this.original.listIterator();
 189  
     }
 190  
 
 191  
     @Override
 192  
     public ListIterator<T> listIterator(final int index) {
 193  0
         return this.original.listIterator(index);
 194  
     }
 195  
 
 196  
     @Override
 197  
     public T remove(final int index) {
 198  0
         throw new UnsupportedOperationException("#remove(int)");
 199  
     }
 200  
 
 201  
     @Override
 202  
     public boolean remove(final Object element) {
 203  0
         throw new UnsupportedOperationException("#remove(Object)");
 204  
     }
 205  
 
 206  
     @Override
 207  
     public boolean removeAll(final Collection<?> elements) {
 208  0
         throw new UnsupportedOperationException("#removeAll(Collection)");
 209  
     }
 210  
 
 211  
     @Override
 212  
     public boolean retainAll(final Collection<?> elements) {
 213  0
         throw new UnsupportedOperationException("#retainAll(Collection)");
 214  
     }
 215  
 
 216  
     @Override
 217  
     public T set(final int index, final T element) {
 218  0
         throw new UnsupportedOperationException("#set(int, T)");
 219  
     }
 220  
 
 221  
     @Override
 222  
     public int size() {
 223  118
         return this.original.size();
 224  
     }
 225  
 
 226  
     /**
 227  
      * {@inheritDoc}
 228  
      *
 229  
      * <p>The method throws {@link ListWrapper.NodeNotFoundException}
 230  
      * when either
 231  
      * {@code start} or {@code end} is bigger than the size of the list. In all
 232  
      * other cases of illegal method call (start is less than zero, end is
 233  
      * less than zero, or start is bigger than end) a standard
 234  
      * {@link IndexOutOfBoundsException} is thrown (by the encapsulated
 235  
      * implementation of {@Link List}).
 236  
      */
 237  
     @Override
 238  
     public List<T> subList(final int start, final int end) {
 239  0
         if (start >= this.size()) {
 240  0
             throw new ListWrapper.NodeNotFoundException(
 241  
                 String.format(
 242  
                     "Start of subList (%d) is out of bounds (size=%d)",
 243  
                     start, this.size()
 244  
                 ),
 245  
                 this.dom,
 246  
                 this.xpath
 247  
             );
 248  
         }
 249  0
         if (end >= this.size()) {
 250  0
             throw new ListWrapper.NodeNotFoundException(
 251  
                 String.format(
 252  
                     "End of subList (%d) is out of bounds (size=%d)",
 253  
                     end, this.size()
 254  
                 ),
 255  
                 this.dom,
 256  
                 this.xpath
 257  
             );
 258  
         }
 259  0
         return this.original.subList(start, end);
 260  
     }
 261  
 
 262  
     @Override
 263  
     public Object[] toArray() {
 264  0
         return this.original.toArray();
 265  
     }
 266  
 
 267  
     @Override
 268  
     @SuppressWarnings("PMD.UseVarargs")
 269  
     public <E> E[] toArray(final E[] array) {
 270  0
         return this.original.toArray(array);
 271  
     }
 272  
 
 273  
     /**
 274  
      * Node not found in XmlDocument.
 275  
      */
 276  
     private static final class NodeNotFoundException
 277  
         extends IndexOutOfBoundsException {
 278  
         /**
 279  
          * Serialization marker.
 280  
          */
 281  
         private static final long serialVersionUID = 0x7526FA78EEDAC470L;
 282  
         /**
 283  
          * Public ctor.
 284  
          * @param message Error message
 285  
          * @param node The XML with error
 286  
          * @param query The query in XPath
 287  
          */
 288  
         NodeNotFoundException(final String message, final Node node,
 289  
             final CharSequence query) {
 290  1
             super(
 291  
                 Logger.format(
 292  
                     "XPath '%s' not found in '%[text]s': %s",
 293  
                     ListWrapper.NodeNotFoundException.escapeUnicode(query),
 294  
                     ListWrapper.NodeNotFoundException.escapeUnicode(
 295  
                         new XMLDocument(node).toString()
 296  
                 ),
 297  
                     message
 298  
             )
 299  
             );
 300  1
         }
 301  
 
 302  
         /**
 303  
          * Escape unicode characters.
 304  
          * @param input Input string
 305  
          * @return Escaped output
 306  
          */
 307  
         private static String escapeUnicode(final CharSequence input) {
 308  2
             final int length = input.length();
 309  2
             final StringBuilder output = new StringBuilder(length);
 310  68
             for (int index = 0; index < length; index += 1) {
 311  66
                 final char character = input.charAt(index);
 312  
                 // @checkstyle MagicNumber (1 line)
 313  66
                 if (character < 32 || character > 0x7f) {
 314  2
                     output.append(String.format("\\u%X", (int) character));
 315  
                 } else {
 316  64
                     output.append(character);
 317  
                 }
 318  
             }
 319  2
             return output.toString();
 320  
         }
 321  
     }
 322  
 
 323  
 }