content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.util;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.io.StringReader;
  import java.sql.Clob;
  import java.sql.SQLException;
  
  import org.apache.cayenne.CayenneRuntimeException;
  
  /**
   * A Clob implementation that stores contents in memory.
   * <p>
   * <i>This implementation is based on jdbcClob from HSQLDB (copyright HSQL Development
   * Group).</i>
   * </p>
   * 
   * @since 1.2
   */
  public class MemoryClob implements Clob {
  
      volatile String data;
  
      /**
       * Constructs a new jdbcClob object wrapping the given character sequence.
       * <p>
       * This constructor is used internally to retrieve result set values as Clob objects,
       * yet it must be public to allow access from other packages. As such (in the interest
       * of efficiency) this object maintains a reference to the given String object rather
       * than making a copy and so it is gently suggested (in the interest of effective
       * memory management) that extenal clients using this constructor either take pause to
       * consider the implications or at least take care to provide a String object whose
       * internal character buffer is not much larger than required to represent the value.
       * 
       * @param data the character sequence representing the Clob value
       * @throws CayenneRuntimeException if the argument is null
       */
      public MemoryClob(String data) {
  
          if (data == null) {
              throw new CayenneRuntimeException("Null data");
          }
  
          this.data = data;
      }
  
      /**
       * Retrieves the number of characters in the <code>CLOB</code> value designated by
       * this <code>Clob</code> object.
       * 
       * @return length of the <code>CLOB</code> in characters
       * @exception SQLException if there is an error accessing the length of the
       *                <code>CLOB</code> value
       */
      public long length() throws SQLException {
  
          final String ldata = data;
  
          return ldata.length();
      }
  
      /**
       * Retrieves a copy of the specified substring in the <code>CLOB</code> value
       * designated by this <code>Clob</code> object. The substring begins at position
       * <code>pos</code> and has up to <code>length</code> consecutive characters.
       */
      public String getSubString(long pos, final int length) throws SQLException {
  
          final String ldata = data;
          final int dlen = ldata.length();
  
          pos--;
  
          if (pos < 0 || pos > dlen) {
              throw new CayenneRuntimeException("Invalid position: %d", (pos + 1L));
          }
  
          if (length < 0 || length > dlen - pos) {
              throw new CayenneRuntimeException("Invalid length: %s", length);
          }
  
          if (pos == 0 && length == dlen) {
              return ldata;
          }
  
          return ldata.substring((int) pos, (int) pos + length);
      }
  
      /**
       * Retrieves the <code>CLOB</code> value designated by this <code>Clob</code>
       * object as a <code>java.io.Reader</code> object (or as a stream of characters).
       * 
       * @return a <code>java.io.Reader</code> object containing the <code>CLOB</code>
       *         data
       * @exception SQLException if there is an error accessing the <code>CLOB</code>
       *                value
       */
      public java.io.Reader getCharacterStream() throws SQLException {
  
          final String ldata = data;
  
          return new StringReader(ldata);
      }
  
      /**
       * Retrieves the <code>CLOB</code> value designated by this <code>Clob</code>
       * object as an ascii stream.
       * 
       * @return a <code>java.io.InputStream</code> object containing the
       *         <code>CLOB</code> data
       * @exception SQLException if there is an error accessing the <code>CLOB</code>
       *                value
       */
      public java.io.InputStream getAsciiStream() throws SQLException {
  
          final String ldata = data;
  
          return new AsciiStringInputStream(ldata);
      }
  
      /**
       * Retrieves the character position at which the specified substring
       * <code>searchstr</code> appears in the SQL <code>CLOB</code> value represented
       * by this <code>Clob</code> object. The search begins at position
       * <code>start</code>.
       * 
       * @param searchstr the substring for which to search
       * @param start the position at which to begin searching; the first position is 1
       * @return the position at which the substring appears or -1 if it is not present; the
       *         first position is 1
       * @exception SQLException if there is an error accessing the <code>CLOB</code>
       *                value
       */
      public long position(final String searchstr, long start) throws SQLException {
  
          if (searchstr == null || start > Integer.MAX_VALUE) {
              return -1;
          }
  
          final String ldata = data;
          final int pos = ldata.indexOf(searchstr, (int) --start);
  
          return (pos < 0) ? -1 : pos + 1;
      }
  
      /**
       * Retrieves the character position at which the specified <code>Clob</code> object
       * <code>searchstr</code> appears in this <code>Clob</code> object. The search
       * begins at position <code>start</code>.
       * 
       * @param searchstr the <code>Clob</code> object for which to search
       * @param start the position at which to begin searching; the first position is 1
       * @return the position at which the <code>Clob</code> object appears or -1 if it is
       *         not present; the first position is 1
       * @exception SQLException if there is an error accessing the <code>CLOB</code>
       *                value
       */
      public long position(final Clob searchstr, long start) throws SQLException {
  
          if (searchstr == null) {
              return -1;
          }
  
          final String ldata = data;
          final long dlen = ldata.length();
          final long sslen = searchstr.length();
  
          // This is potentially much less expensive than materializing a large
          // substring from some other vendor's CLOB. Indeed, we should probably
          // do the comparison piecewise, using an in-memory buffer (or temp-files
          // when available), if it is detected that the input CLOB is very long.
          if (start > dlen - sslen) {
              return -1;
          }
  
          // by now, we know sslen and start are both < Integer.MAX_VALUE
          String s;
  
          if (searchstr instanceof MemoryClob) {
              s = ((MemoryClob) searchstr).data;
          }
          else {
              s = searchstr.getSubString(1L, (int) sslen);
          }
  
          final int pos = ldata.indexOf(s, (int) start);
  
          return (pos < 0) ? -1 : pos + 1;
      }
  
      /**
       * Writes the given Java <code>String</code> to the <code>CLOB</code> value that
       * this <code>Clob</code> object designates at the position <code>pos</code>.
       * Calling this method always throws an <code>SQLException</code>.
       */
      public int setString(long pos, String str) throws SQLException {
          throw new CayenneRuntimeException("Not supported");
      }
  
      /**
       * Writes <code>len</code> characters of <code>str</code>, starting at character
       * <code>offset</code>, to the <code>CLOB</code> value that this
       * <code>Clob</code> represents. Calling this method always throws an
       * <code>SQLException</code>.
       */
      public int setString(long pos, String str, int offset, int len) throws SQLException {
          throw new CayenneRuntimeException("Not supported");
      }
  
      /**
       * Retrieves a stream to be used to write Ascii characters to the <code>CLOB</code>
       * value that this <code>Clob</code> object represents, starting at position
       * <code>pos</code>.
       * <p>
       * Calling this method always throws an <code>SQLException</code>.
       */
      public java.io.OutputStream setAsciiStream(long pos) throws SQLException {
          throw new CayenneRuntimeException("Not supported");
      }
  
      /**
       * Retrieves a stream to be used to write a stream of Unicode characters to the
       * <code>CLOB</code> value that this <code>Clob</code> object represents, at
       * position <code>pos</code>.
       * <p>
       * Calling this method always throws an <code>SQLException</code>.
       */
      public java.io.Writer setCharacterStream(long pos) throws SQLException {
          throw new CayenneRuntimeException("Not supported");
      }
  
      /**
       * Truncates the <code>CLOB</code> value that this <code>Clob</code> designates to
       * have a length of <code>len</code> characters.
       * <p>
       */
      public void truncate(final long len) throws SQLException {
  
          final String ldata = data;
          final long dlen = ldata.length();
          final long chars = len >> 1;
  
          if (chars == dlen) {
              // nothing has changed, so there's nothing to be done
          } else if (len < 0 || chars > dlen) {
              throw new CayenneRuntimeException("Invalid length: %d", len);
          } else {
              // use new String() to ensure we get rid of slack
              data = ldata.substring(0, (int) chars);
          }
      }
  
      class AsciiStringInputStream extends InputStream {
  
          protected int strOffset = 0;
          protected int charOffset = 0;
          protected int available;
          protected String str;
  
          public AsciiStringInputStream(String s) {
              str = s;
              available = s.length() * 2;
          }
  
          public int doRead() throws IOException {
  
              if (available == 0) {
                  return -1;
              }
  
              available--;
  
              char c = str.charAt(strOffset);
  
              if (charOffset == 0) {
                  charOffset = 1;
  
                  return (c & 0x0000ff00) >> 8;
              }
              else {
                  charOffset = 0;
                  strOffset++;
                  return c & 0x000000ff;
              }
          }
  
          @Override
          public int read() throws IOException {
              doRead();
              return doRead();
          }
  
          @Override
          public int available() throws IOException {
              return available / 2;
          }
      }
  
      /**
       * @since 3.0
       */
      // JDBC 4 compatibility under Java 1.5
      public void free() throws SQLException {
      }
  
      /**
       * @since 3.0
       */
      // JDBC 4 compatibility under Java 1.5
      public Reader getCharacterStream(long pos, long length) throws SQLException {
          throw new UnsupportedOperationException();
      }
  }
  