1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.jimage;
  27 
  28 import java.io.ByteArrayInputStream;
  29 import java.io.ByteArrayOutputStream;
  30 import java.io.DataInputStream;
  31 import java.io.DataOutputStream;
  32 import java.io.IOException;
  33 import java.nio.ByteBuffer;
  34 import java.nio.ByteOrder;
  35 import java.nio.charset.Charset;
  36 import java.util.Arrays;
  37 
  38 public final class UTF8String implements CharSequence {
  39     // Same as StandardCharsets.UTF_8 without loading all of the standard charsets
  40     static final Charset UTF_8 = Charset.forName("UTF-8");
  41 
  42     public static final int NOT_FOUND = -1;
  43     public static final int HASH_MULTIPLIER = 0x01000193;
  44     public static final UTF8String EMPTY_STRING = new UTF8String("");
  45     public static final UTF8String SLASH_STRING = new UTF8String("/");
  46     public static final UTF8String DOT_STRING = new UTF8String(".");
  47 
  48     // TODO This strings are implementation specific and should be defined elsewhere.
  49     public static final UTF8String MODULES_STRING = new UTF8String("/modules");
  50     public static final UTF8String PACKAGES_STRING = new UTF8String("/packages");
  51 
  52     final byte[] bytes;
  53     final int offset;
  54     final int count;
  55     int hashcode;
  56 
  57     public UTF8String(byte[] bytes, int offset, int count) {
  58         if (offset < 0 || count < 0 || (offset + count) > bytes.length) {
  59             throw new IndexOutOfBoundsException("offset/count out of range");
  60         }
  61         this.bytes = bytes;
  62         this.offset = offset;
  63         this.count = count;
  64         this.hashcode = -1;
  65     }
  66 
  67     public UTF8String(byte[] bytes, int offset) {
  68         this(bytes, offset, bytes.length - offset);
  69     }
  70 
  71     public UTF8String(byte[] bytes) {
  72         this(bytes, 0, bytes.length);
  73     }
  74 
  75     public UTF8String(String string) {
  76         this(stringToBytes(string));
  77     }
  78 
  79     @Override
  80     public int length() {
  81         return count;
  82     }
  83 
  84     public boolean isEmpty() {
  85         return count == 0;
  86     }
  87 
  88     public int byteAt(int index) {
  89         return bytes[offset + index] & 0xFF;
  90     }
  91 
  92     public UTF8String concat(UTF8String s) {
  93         int total = count + s.count;
  94         byte[] combined = new byte[total];
  95         System.arraycopy(bytes, offset, combined, 0, count);
  96         System.arraycopy(s.bytes, s.offset, combined, count, s.count);
  97 
  98         return new UTF8String(combined, 0, total);
  99     }
 100 
 101     public UTF8String concat(UTF8String... s) {
 102         int total = count;
 103 
 104         for (UTF8String i : s) {
 105             total += i.count;
 106         }
 107 
 108         byte[] combined = new byte[total];
 109         System.arraycopy(bytes, offset, combined, 0, count);
 110         int next = count;
 111 
 112         for (UTF8String i : s) {
 113             System.arraycopy(i.bytes, i.offset, combined, next, i.count);
 114             next += i.count;
 115         }
 116 
 117         return new UTF8String(combined, 0, total);
 118     }
 119 
 120     public UTF8String substring(int offset) {
 121         return substring(offset, this.count - offset);
 122     }
 123 
 124     public UTF8String substring(int offset, int count) {
 125         int newOffset = this.offset + offset;
 126         return new UTF8String(bytes, newOffset, count);
 127     }
 128 
 129     public UTF8String trimToSize() {
 130         return offset == 0 && bytes.length == count ? this :
 131                new UTF8String(Arrays.copyOfRange(bytes, offset, offset + count));
 132     }
 133 
 134     public int indexOf(int ch) {
 135         return indexOf(ch, 0);
 136     }
 137 
 138     public int indexOf(int ch, int start) {
 139         for (int i = Math.max(start, 0); i < count; i++) {
 140             if (byteAt(i) == ch) {
 141                 return i;
 142             }
 143         }
 144 
 145         return NOT_FOUND;
 146     }
 147 
 148     public int lastIndexOf(int ch) {
 149         return lastIndexOf(ch, count - 1);
 150     }
 151 
 152     public int lastIndexOf(int ch, int start) {
 153         for (int i = Math.min(start, count); i > 0; i--) {
 154             if (byteAt(i) == ch) {
 155                 return i;
 156             }
 157         }
 158 
 159         return NOT_FOUND;
 160     }
 161 
 162     public void writeTo(ImageStream buffer) {
 163         buffer.put(bytes, offset, count);
 164     }
 165 
 166     public static int hashCode(int seed, byte[] bytes, int offset, int count) {
 167         for (int i = offset, limit = offset + count; i < limit; i++) {
 168             seed = (seed * HASH_MULTIPLIER) ^ (bytes[i] & 0xFF);
 169         }
 170 
 171         return seed & 0x7FFFFFFF;
 172     }
 173 
 174     public int hashCode(int seed) {
 175         return hashCode(seed, bytes, offset, count);
 176     }
 177 
 178     @Override
 179     public int hashCode() {
 180         if (hashcode < 0) {
 181             hashcode = hashCode(HASH_MULTIPLIER, bytes, offset, count);
 182         }
 183 
 184         return hashcode;
 185     }
 186 
 187     @Override
 188     public boolean equals(Object obj) {
 189         if (obj == null) {
 190             return false;
 191         }
 192 
 193         if (getClass() != obj.getClass()) {
 194             return false;
 195         }
 196 
 197         return equals(this, (UTF8String)obj);
 198     }
 199 
 200     public static boolean equals(UTF8String a, UTF8String b) {
 201         if (a == b) {
 202             return true;
 203         }
 204 
 205         int count = a.count;
 206 
 207         if (count != b.count) {
 208             return false;
 209         }
 210 
 211         byte[] aBytes = a.bytes;
 212         byte[] bBytes = b.bytes;
 213         int aOffset = a.offset;
 214         int bOffset = b.offset;
 215 
 216         for (int i = 0; i < count; i++) {
 217             if (aBytes[aOffset + i] != bBytes[bOffset + i]) {
 218                 return false;
 219             }
 220         }
 221 
 222         return true;
 223     }
 224 
 225     public byte[] getBytesCopy() {
 226         return Arrays.copyOfRange(bytes, offset, offset + count);
 227     }
 228 
 229     byte[] getBytes() {
 230         if (offset != 0 || bytes.length != count) {
 231             return Arrays.copyOfRange(bytes, offset, offset + count);
 232         }
 233 
 234         return bytes;
 235     }
 236 
 237     /**
 238      * Convert the string bytes into Modified UTF-8 encoding (as defined in
 239      * <code>java.io.DataInput</code>
 240      * @param string
 241      * @return bytes encoded into modified UTF-8
 242      */
 243     private static byte[] stringToBytes(String string) {
 244         try {
 245             ByteArrayOutputStream bos = new ByteArrayOutputStream();
 246             DataOutputStream ss = new DataOutputStream(bos);
 247             ss.writeUTF(string);
 248             byte[] content = bos.toByteArray();
 249             // first 2 items are length;
 250             if(content.length <= 2) {
 251                 return new byte[0];
 252             }
 253             return Arrays.copyOfRange(content, 2, content.length);
 254         } catch (IOException ex) {
 255             throw new RuntimeException(ex);
 256         }
 257     }
 258 
 259     @Override
 260     public String toString() {
 261         ByteBuffer buffer = ByteBuffer.allocate(bytes.length+2);
 262         buffer.order(ByteOrder.BIG_ENDIAN);
 263         buffer.putShort((short)bytes.length);
 264         buffer.put(bytes);
 265         ByteArrayInputStream stream = new ByteArrayInputStream(buffer.array());
 266         DataInputStream in = new DataInputStream(stream);
 267         try {
 268             return in.readUTF();
 269         } catch (IOException ex) {
 270             throw new RuntimeException(ex);
 271         }
 272     }
 273 
 274     @Override
 275     public char charAt(int index) {
 276         int ch = byteAt(index);
 277 
 278         return (ch & 0x80) == 0 ? (char)ch : '\0';
 279     }
 280 
 281     @Override
 282     public CharSequence subSequence(int start, int end) {
 283         return (CharSequence)substring(start, end - start);
 284     }
 285 }