/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed 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 com.intellij.vcs.log.impl; import com.intellij.util.io.DataInputOutputUtil; import com.intellij.vcs.log.Hash; import org.jetbrains.annotations.NotNull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Arrays; /** * <p>The {@link Hash} implementation which stores a hash in a byte array thus saving some memory.</p> */ public class HashImpl implements Hash { private static final int BASE = 16; private static final int SHORT_HASH_LENGTH = 7; @NotNull private final byte[] myData; private final int myHashCode; @NotNull public static Hash build(@NotNull String inputStr) { byte[] data = buildData(inputStr); assert data.length > 0 : "Can not build hash for string " + inputStr; return new HashImpl(data); } @NotNull public static Hash read(@NotNull DataInput in) throws IOException { int length = DataInputOutputUtil.readINT(in); if (length == 0) throw new IOException("Can not read hash: data length is zero"); byte[] buf = new byte[length]; in.readFully(buf); return new HashImpl(buf); } public void write(@NotNull DataOutput out) throws IOException { DataInputOutputUtil.writeINT(out, myData.length); out.write(myData); } @NotNull private static byte[] buildData(@NotNull String inputStr) { // if length == 5, need 3 byte + 1 signal byte int length = inputStr.length(); byte even = (byte)(length % 2); byte[] data = new byte[length / 2 + 1 + even]; data[0] = even; for (int i = 0; i < length / 2; i++) { int k = parseChar(inputStr, 2 * i) * BASE + parseChar(inputStr, 2 * i + 1); data[i + 1] = (byte)(k - 128); } if (even == 1) { int k = parseChar(inputStr, length - 1); data[length / 2 + 1] = (byte)(k - 128); } return data; } private static int parseChar(@NotNull String inputString, int index) { int k = Character.digit(inputString.charAt(index), BASE); if (k < 0) { throw new IllegalArgumentException("bad hash string: " + inputString); } return k; } private HashImpl(@NotNull byte[] hash) { myData = hash; myHashCode = Arrays.hashCode(hash); } @NotNull @Override public String asString() { assert myData.length > 0 : "bad length Hash.data"; byte even = myData[0]; StringBuilder sb = new StringBuilder(); for (int i = 1; i < myData.length; i++) { int k1 = (myData[i] + 128) / 16; int k2 = (myData[i] + 128) % 16; char c1 = Character.forDigit(k1, 16); char c2 = Character.forDigit(k2, 16); if (i == myData.length - 1 && even == 1) { sb.append(c2); } else { sb.append(c1).append(c2); } } return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HashImpl that = (HashImpl)o; return myHashCode == that.myHashCode && Arrays.equals(myData, that.myData); } @Override public int hashCode() { return myHashCode; } @Override public String toString() { return asString(); } @NotNull @Override public String toShortString() { String s = asString(); return s.substring(0, Math.min(s.length(), SHORT_HASH_LENGTH)); } }