Coverage Report - com.damnhandy.uri.template.UriUtil
 
Classes in this File Line Coverage Branch Coverage Complexity
UriUtil
96%
58/60
100%
12/12
2.75
 
 1  
 /*
 2  
  * Copyright 2012, Ryan J. McDonough
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package com.damnhandy.uri.template;
 17  
 
 18  
 import java.io.ByteArrayOutputStream;
 19  
 import java.io.UnsupportedEncodingException;
 20  
 import java.nio.charset.Charset;
 21  
 import java.util.ArrayList;
 22  
 import java.util.BitSet;
 23  
 import java.util.List;
 24  
 import java.util.regex.Matcher;
 25  
 import java.util.regex.Pattern;
 26  
 
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * A light-weight utility class for applying encoding to values that are applied to
 31  
  * expression values.
 32  
  * </p>
 33  
  *
 34  
  * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a>
 35  
  * @version $Revision: 1.1 $
 36  
  */
 37  
 public final class UriUtil
 38  
 {
 39  2
     static final Pattern PCT_ENCODDED_STRING = Pattern.compile("%[0-9A-Fa-f]{2}");
 40  
 
 41  2
     static final char[] GENERAL_DELIM_CHARS = {':', '/', ',', '?', '#', '[', ']', '@'};
 42  
 
 43  2
     static final char[] SUB_DELIMS_CHARS = {'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '<', '>', '{', '}'};
 44  
 
 45  
     private static final BitSet RESERVED;
 46  
 
 47  
     private static final BitSet ESCAPE_CHARS;
 48  
 
 49  
     static
 50  
     {
 51  
 
 52  2
         RESERVED = new BitSet();
 53  18
         for (int i = 0; i < GENERAL_DELIM_CHARS.length; i++)
 54  
         {
 55  16
             RESERVED.set(GENERAL_DELIM_CHARS[i]);
 56  
         }
 57  2
         RESERVED.set(' ');
 58  2
         RESERVED.set('%');
 59  2
         RESERVED.set('|');
 60  2
         RESERVED.set('\\');
 61  2
         RESERVED.set('`');
 62  2
         RESERVED.set('"');
 63  2
         RESERVED.set('^');
 64  
 
 65  32
         for (int i = 0; i < SUB_DELIMS_CHARS.length; i++)
 66  
         {
 67  30
             RESERVED.set(SUB_DELIMS_CHARS[i]);
 68  
         }
 69  
 
 70  2
         ESCAPE_CHARS = new BitSet();
 71  2
         ESCAPE_CHARS.set('<');
 72  2
         ESCAPE_CHARS.set('>');
 73  2
         ESCAPE_CHARS.set('%');
 74  2
         ESCAPE_CHARS.set('\"');
 75  2
         ESCAPE_CHARS.set('{');
 76  2
         ESCAPE_CHARS.set('}');
 77  2
         ESCAPE_CHARS.set('|');
 78  2
         ESCAPE_CHARS.set('\\');
 79  2
         ESCAPE_CHARS.set('^');
 80  2
         ESCAPE_CHARS.set('[');
 81  2
         ESCAPE_CHARS.set(']');
 82  2
         ESCAPE_CHARS.set('`');
 83  2
     }
 84  
 
 85  
     private UriUtil()
 86  0
     {
 87  
 
 88  0
     }
 89  
 
 90  
     /**
 91  
      * @param sourceValue
 92  
      * @return the encoded string
 93  
      */
 94  
     public static String encodeFragment(String sourceValue) throws UnsupportedEncodingException
 95  
     {
 96  
         // Check if the string has %-encoded values already.
 97  
         // if it does, rebuild the string and encode the non-encoded bit
 98  
         // but don't re-encode the already encoded strings.
 99  
         //
 100  
         // There's probably a cleaner way to do this.
 101  506
         Matcher m = PCT_ENCODDED_STRING.matcher(sourceValue);
 102  506
         List<int[]> positions = new ArrayList<int[]>();
 103  516
         while (m.find())
 104  
         {
 105  10
             positions.add(new int[]{m.start(), m.end()});
 106  
         }
 107  506
         if(!positions.isEmpty())
 108  
         {
 109  10
             StringBuilder b = new StringBuilder();
 110  10
             int offset = 0;
 111  10
             for (int[] pos : positions)
 112  
             {
 113  
                 // encode the non-encoded portion of the string
 114  10
                 b.append(UriUtil.encode(sourceValue.substring(offset, pos[0]),ESCAPE_CHARS));
 115  
                 // the already encodede string does not get encoded twice
 116  10
                 b.append(sourceValue.substring(pos[0], pos[1]));
 117  10
                 offset = pos[1];
 118  10
             }
 119  10
             b.append(encode(sourceValue.substring(offset, sourceValue.length()),ESCAPE_CHARS));
 120  10
             return b.toString();
 121  
         }
 122  
         // If there's
 123  496
         return encode(sourceValue, ESCAPE_CHARS);
 124  
     }
 125  
 
 126  
     /**
 127  
      * @param sourceValue
 128  
      * @return the encoded string
 129  
      */
 130  
     public static String encode(String sourceValue) throws UnsupportedEncodingException
 131  
     {
 132  1572
         return encode(sourceValue, RESERVED);
 133  
     }
 134  
 
 135  
     /**
 136  
      * @param sourceValue
 137  
      * @param chars
 138  
      * @return the encoded string
 139  
      * @throws UnsupportedEncodingException
 140  
      */
 141  
     private static String encode(String sourceValue, BitSet chars) throws UnsupportedEncodingException
 142  
     {
 143  2088
         ByteArrayOutputStream out = new ByteArrayOutputStream();
 144  2088
         byte[] source = sourceValue.getBytes(Charset.forName("UTF-8"));
 145  11176
         for (int i = 0; i < source.length; i++)
 146  
         {
 147  9088
             byte c = source[i];
 148  
             // fixed unsigned problem
 149  9088
             if (chars.get(c & 0xff) || c <= 0x20)
 150  
             {
 151  880
                 out.write('%');
 152  880
                 char hex1 = Character.toUpperCase(Character.forDigit((c >> 4) & 0xF, 16));
 153  880
                 char hex2 = Character.toUpperCase(Character.forDigit(c & 0xF, 16));
 154  880
                 out.write(hex1);
 155  880
                 out.write(hex2);
 156  880
             }
 157  
             else
 158  
             {
 159  8208
                 out.write(c);
 160  
             }
 161  
         }
 162  2088
         return new String(out.toByteArray(), "UTF-8");
 163  
     }
 164  
 
 165  
 }