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
   *
   * http://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.chemistry.opencmis.commons.impl.dataobjects;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Locale;
  
  import org.apache.chemistry.opencmis.commons.data.ContentStreamHash;
  
  public class ContentStreamHashImpl implements ContentStreamHash {
  
      public static final String ALGORITHM_MD5 = "md5";
      public static final String ALGORITHM_SHA1 = "sha-1";
      public static final String ALGORITHM_SHA224 = "sha-224";
      public static final String ALGORITHM_SHA256 = "sha-256";
      public static final String ALGORITHM_SHA384 = "sha-384";
      public static final String ALGORITHM_SHA512 = "sha-512";
      public static final String ALGORITHM_SHA3 = "sha-3";
  
      private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
  
      private String propertyValue;
      private String algorithm = null;
      private String hash = null;
  
      /**
       * Constructs an object from the {@code cmis:contentStreamHash} property
       * value.
       * 
       * @param propertyValue
       *            the property value
       */
      public ContentStreamHashImpl(String propertyValue) {
          this.propertyValue = propertyValue;
  
          if (propertyValue == null) {
              return;
          }
  
          String pv = propertyValue.trim();
          int algEnd = pv.indexOf('}');
          if (pv.charAt(0) != '{' || algEnd < 1) {
              return;
          }
  
          this.algorithm = pv.substring(1, algEnd).toLowerCase(Locale.ENGLISH);
          this.hash = pv.substring(algEnd + 1).replaceAll("\\s", "").toLowerCase(Locale.ENGLISH);
      }
  
      /**
       * Constructs an object from the algorithm and hash.
       * 
       * @param algorithm
       *            the algorithm
       * @param hashStr
       *            the hash value
       */
      public ContentStreamHashImpl(String algorithm, String hashStr) {
          if (algorithm == null || algorithm.trim().length() == 0) {
              throw new IllegalArgumentException("Algorithm must be set!");
          }
  
          if (hashStr == null || hashStr.trim().length() == 0) {
              throw new IllegalArgumentException("Hash must be set!");
          }
  
          this.algorithm = algorithm.toLowerCase(Locale.ENGLISH);
          this.hash = hashStr.replaceAll("\\s", "").toLowerCase(Locale.ENGLISH);
          this.propertyValue = "{" + this.algorithm + "}" + this.hash;
      }
  
      /**
       * Constructs an object from the algorithm and hash.
       * 
       * @param algorithm
       *            the algorithm
       * @param hashBytes
       *            the hash value as byte array
       */
      public ContentStreamHashImpl(String algorithm, byte[] hashBytes) {
          if (algorithm == null || algorithm.trim().length() == 0) {
              throw new IllegalArgumentException("Algorithm must be set!");
          }
  
          if (hashBytes == null || hashBytes.length == 0) {
              throw new IllegalArgumentException("Hash must be set!");
          }
  
          this.algorithm = algorithm.toLowerCase(Locale.ENGLISH);
          this.hash = byteArrayToHexString(hashBytes);
          this.propertyValue = "{" + this.algorithm + "}" + this.hash;
      }
  
      @Override
      public String getPropertyValue() {
          return propertyValue;
      }
  
      @Override
      public String getAlgorithm() {
          return algorithm;
      }
  
      @Override
      public String getHash() {
          return hash;
      }
  
      @Override
      public int hashCode() {
          return propertyValue.hashCode();
      }
  
      @Override
      public boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
          if (obj == null) {
              return false;
          }
          if (getClass() != obj.getClass()) {
              return false;
          }
  
          ContentStreamHashImpl other = (ContentStreamHashImpl) obj;
          if (propertyValue == null) {
              if (other.propertyValue != null) {
                  return false;
              }
          } else if (!propertyValue.equals(other.propertyValue)) {
              return false;
          }
  
          return true;
      }
  
      @Override
      public String toString() {
          return propertyValue;
      }
  
      /**
       * Creates a list of content hashes from a stream
       * <p>
       * This method consumes the stream but doesn't close it.
       * 
       * @param stream
       *            the stream
       * @param algorithm
       *            the algorithms
       * @return the list of content hashes
       */
      public static List<ContentStreamHash> createContentStreamHashes(InputStream stream, String... algorithm)
              throws IOException, NoSuchAlgorithmException {
          if (stream == null) {
              throw new IllegalArgumentException("Stream must be set!");
          }
  
          if (algorithm == null || algorithm.length == 0) {
              throw new IllegalArgumentException("Algorithm must be set!");
          }
  
          MessageDigest[] md = new MessageDigest[algorithm.length];
          for (int i = 0; i < algorithm.length; i++) {
              md[i] = MessageDigest.getInstance(algorithm[i]);
          }
  
          int b;
          byte[] buffer = new byte[64 * 1024];
          while ((b = stream.read(buffer)) > -1) {
              for (int j = 0; j < md.length; j++) {
                  md[j].update(buffer, 0, b);
              }
          }
  
          List<ContentStreamHash> result = new ArrayList<ContentStreamHash>(md.length);
  
          for (int i = 0; i < md.length; i++) {
              result.add(new ContentStreamHashImpl(algorithm[i], md[i].digest()));
          }
  
          return result;
      }
  
      protected static String byteArrayToHexString(byte[] bytes) {
          int n = bytes.length;
          char[] hashHex = new char[n * 2];
          for (int i = 0; i < n; i++) {
              hashHex[i * 2] = HEX_DIGITS[(0xF0 & bytes[i]) >>> 4];
              hashHex[i * 2 + 1] = HEX_DIGITS[0x0F & bytes[i]];
          }
  
          return new String(hashHex);
      }
  }
  