/* * Copyright 2011 Robert W. Vawter III <bob@vawter.org> * * 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 org.jsonddl.impl; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.jsonddl.JsonDdlObject; import org.jsonddl.JsonDdlVisitor.PropertyVisitor; import org.jsonddl.model.Kind; /** * Computes a MessageDigest. */ public class DigestVisitor implements PropertyVisitor { private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static final Charset UTF8 = Charset.forName("UTF8"); private final MessageDigest sha; private final ByteBuffer temp = ByteBuffer.allocate(8); public DigestVisitor() { try { sha = MessageDigest.getInstance("SHA1"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA1 unsupported", e); } } public DigestVisitor(String digestName) throws NoSuchAlgorithmException { sha = MessageDigest.getInstance(digestName); } public void endVisit(JsonDdlObject<?> x) { sha.update(x.getDdlObjectType().getName().getBytes(UTF8)); } @Override public <T> void endVisitProperty(T value, Context<T> ctx) { update(ctx.getProperty()); update(value, ctx.getKind(), ctx.getNestedKinds()); } /** * Returns the computed digest and resets the state of the visitor. */ public byte[] getDigest() { return sha.digest(); } /** * Returns the computed digest as a hexadecimal-encoded string and resets the state of the * visitor. */ public String getDigestHex() { StringBuilder sb = new StringBuilder(); for (byte b : getDigest()) { char lsc = HEX_DIGITS[b & 0xF]; char msc = HEX_DIGITS[b >>> 4]; sb.append(msc).append(lsc); } return sb.toString(); } @Override public <T> boolean visitProperty(T value, Context<T> ctx) { return false; } private void update(double d) { temp.rewind(); temp.putDouble(d); temp.rewind(); temp.limit(8); sha.update(temp); } private void update(int i) { temp.rewind(); temp.putInt(i); temp.rewind(); temp.limit(4); sha.update(temp); } private void update(Object value, Kind kind, List<Kind> nestedKinds) { if (value == null) { return; } switch (kind) { case BOOLEAN: sha.update(((Boolean) value).booleanValue() ? (byte) 1 : 0); break; case DDL: sha.update(((Digested) value).computeDigest()); break; case DOUBLE: update((Double) value); break; case ENUM: update(((Enum<?>) value).name()); break; case INTEGER: update((Integer) value); break; case STRING: update(value.toString()); break; case LIST: { List<?> list = (List<?>) value; update(list.size()); List<Kind> newKinds = new ArrayList<Kind>(nestedKinds); Kind newKind = newKinds.remove(0); for (Object o : list) { update(o, newKind, newKinds); } break; } case MAP: { Map<?, ?> map = (Map<?, ?>) value; update(map.size()); List<Kind> newKinds = new ArrayList<Kind>(nestedKinds); if (!Kind.STRING.equals(newKinds.remove(0))) { throw new RuntimeException("Did not find expected String kind"); } Kind newKind = newKinds.remove(0); for (Map.Entry<?, ?> entry : map.entrySet()) { update(entry.getKey().toString()); update(entry.getValue(), newKind, newKinds); } break; } case EXTERNAL: if (value instanceof Digested) { sha.update(((Digested) value).computeDigest()); } else { // Fake it. update(value.getClass().getName()); update(value.hashCode()); update(value.toString()); } break; default: throw new RuntimeException("Unhandled kind " + kind); } } private void update(String s) { sha.update(s.getBytes(UTF8)); } }