1 /*
  2  * Copyright (c) 2025, 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 oracle.code.json;
 27 
 28 import java.util.Objects;
 29 
 30 /**
 31  * JsonString implementation class
 32  */
 33 final class JsonStringImpl implements JsonString, JsonValueImpl {
 34 
 35     private final JsonDocumentInfo docInfo;
 36     private final int startOffset;
 37     private final int endOffset;
 38     private final int endIndex;
 39     private String theString;
 40     private String source;
 41 
 42     JsonStringImpl(String str) {
 43         docInfo = new JsonDocumentInfo(("\"" + str + "\"").toCharArray());
 44         startOffset = 0;
 45         endOffset = docInfo.getEndOffset();
 46         theString = unescape(startOffset + 1, endOffset - 1);
 47         endIndex = 0;
 48     }
 49 
 50     JsonStringImpl(JsonDocumentInfo doc, int offset, int index) {
 51         docInfo = doc;
 52         startOffset = offset;
 53         endIndex = index + 1;
 54         endOffset = docInfo.getOffset(endIndex) + 1;
 55     }
 56 
 57     @Override
 58     public String value() {
 59         if (theString == null) {
 60             theString = unescape(startOffset + 1, endOffset - 1);
 61         }
 62         return theString;
 63     }
 64 
 65     @Override
 66     public int getEndIndex() {
 67         return endIndex + 1; // We are interested in the index after '"'
 68     }
 69 
 70     @Override
 71     public boolean equals(Object o) {
 72         return this == o ||
 73             o instanceof JsonStringImpl ojsi &&
 74             Objects.equals(toString(), ojsi.toString());
 75     }
 76 
 77     @Override
 78     public int hashCode() {
 79         return Objects.hash(toString());
 80     }
 81 
 82     @Override
 83     public String toUntyped() {
 84         return value();
 85     }
 86 
 87     @Override
 88     public String toString() {
 89         if (source == null) {
 90             source = docInfo.substring(startOffset, endOffset);
 91         }
 92         return source;
 93     }
 94 
 95     String unescape(int startOffset, int endOffset) {
 96         var sb = new StringBuilder();
 97         var escape = false;
 98         int offset = startOffset;
 99         for (; offset < endOffset; offset++) {
100             var c = docInfo.charAt(offset);
101             if (escape) {
102                 switch (c) {
103                     case '"', '\\', '/' -> {}
104                     case 'b' -> c = '\b';
105                     case 'f' -> c = '\f';
106                     case 'n' -> c = '\n';
107                     case 'r' -> c = '\r';
108                     case 't' -> c = '\t';
109                     case 'u' -> {
110                         c = JsonParser.codeUnit(docInfo, offset + 1);
111                         offset += 4;
112                     }
113                     // TBD: should be replaced with appropriate runtime exception
114                     default -> throw new RuntimeException("Illegal escape sequence");
115                 }
116                 escape = false;
117             } else if (c == '\\') {
118                 escape = true;
119                 continue;
120             }
121             sb.append(c);
122         }
123         return sb.toString();
124     }
125 }