Index: lutinutil/src/java/org/codelutin/util/DigestGenerator.java diff -u /dev/null lutinutil/src/java/org/codelutin/util/DigestGenerator.java:1.1 --- /dev/null Tue Feb 12 18:10:54 2008 +++ lutinutil/src/java/org/codelutin/util/DigestGenerator.java Tue Feb 12 18:10:47 2008 @@ -0,0 +1,362 @@ +/* + * 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. + */ + +/* + * Modified by Landais Gabriel, Code Lutin 2008 + * + * Works with standard org.w3c.dom XML classes + * + */ + +package org.codelutin.util; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Helper class to provide the functionality of the digest value generation. This is an implementation of the DHASH + * algorithm on . + */ +public class DigestGenerator { + + /** + * This method is an overloaded method for the digest generation for Document + * + * @param document + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest + */ + public byte[] getDigest(Document document, String digestAlgorithm) + throws Exception { + byte[] digest = new byte[0]; + try { + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeInt(9); + Collection childNodes = getValidElements(document); + dos.writeInt(childNodes.size()); + Iterator itr = childNodes.iterator(); + while (itr.hasNext()) { + Node node = (Node) itr.next(); + if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) + dos.write(getDigest((ProcessingInstruction) node, + digestAlgorithm)); + else if (node.getNodeType() == Node.ELEMENT_NODE) + dos.write(getDigest((Element) node, digestAlgorithm)); + } + dos.close(); + md.update(baos.toByteArray()); + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new Exception(e); + } catch (IOException e) { + throw new Exception(e); + } + return digest; + } + + /** + * This method is an overloaded method for the digest generation for Node + * + * @param node + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest value + */ + public byte[] getDigest(Node node, String digestAlgorithm) throws Exception { + if (node.getNodeType() == Node.ELEMENT_NODE) + return getDigest((Element) node, digestAlgorithm); + else if (node.getNodeType() == Node.TEXT_NODE) + return getDigest((Text) node, digestAlgorithm); + else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) + return getDigest((ProcessingInstruction) node, digestAlgorithm); + else + return new byte[0]; + } + + /** + * This method is an overloaded method for the digest generation for Element + * + * @param element + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest value + */ + public byte[] getDigest(Element element, String digestAlgorithm) + throws Exception { + byte[] digest = new byte[0]; + try { + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeInt(1); + dos.write(getExpandedName(element).getBytes("UnicodeBigUnmarked")); + dos.write((byte) 0); + dos.write((byte) 0); + Collection attrs = getAttributesWithoutNS(element); + dos.writeInt(attrs.size()); + Iterator itr = attrs.iterator(); + while (itr.hasNext()) + dos.write(getDigest((Attr) itr.next(), digestAlgorithm)); + Node node = element.getFirstChild(); + // adjoining Texts are merged, + // there is no 0-length Text, and + // comment nodes are removed. + int length = element.getChildNodes().getLength(); + dos.writeInt(length); + while (node != null) { + dos.write(getDigest(node, digestAlgorithm)); + node = node.getNextSibling(); + } + dos.close(); + md.update(baos.toByteArray()); + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new Exception(e); + } catch (IOException e) { + throw new Exception(e); + } + return digest; + } + + /** + * This method is an overloaded method for the digest generation for ProcessingInstruction + * + * @param pi + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest value + */ + public byte[] getDigest(ProcessingInstruction pi, String digestAlgorithm) + throws Exception { + byte[] digest = new byte[0]; + try { + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 7); + md.update(pi.getTarget().getBytes("UnicodeBigUnmarked")); + md.update((byte) 0); + md.update((byte) 0); + md.update(pi.getNodeValue().getBytes("UnicodeBigUnmarked")); + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new Exception(e); + } catch (UnsupportedEncodingException e) { + throw new Exception(e); + } + return digest; + } + + /** + * This method is an overloaded method for the digest generation for Attr + * + * @param attribute + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest value + */ + public byte[] getDigest(Attr attribute, String digestAlgorithm) + throws Exception { + byte[] digest = new byte[0]; + if (!(attribute.getLocalName().equals("xmlns") || attribute + .getLocalName().startsWith("xmlns:"))) + try { + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 2); + md.update(getExpandedName(attribute).getBytes( + "UnicodeBigUnmarked")); + md.update((byte) 0); + md.update((byte) 0); + md.update(attribute.getValue().getBytes("UnicodeBigUnmarked")); + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new Exception(e); + } catch (UnsupportedEncodingException e) { + throw new Exception(e); + } + return digest; + } + + /** + * This method is an overloaded method for the digest generation for Text + * + * @param text + * @param digestAlgorithm + * @return Returns a byte array representing the calculated digest value + */ + public byte[] getDigest(Text text, String digestAlgorithm) throws Exception { + byte[] digest = new byte[0]; + try { + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 0); + md.update((byte) 3); + md.update(text.getTextContent().getBytes("UnicodeBigUnmarked")); + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new Exception(e); + } catch (UnsupportedEncodingException e) { + throw new Exception(e); + } + return digest; + } + + /** + * This method is an overloaded method for getting the expanded name namespaceURI followed by the local name for + * Element + * + * @param element + * @return Returns the expanded name of Element + */ + public String getExpandedName(Element element) { + return element.getNamespaceURI() + ":" + element.getLocalName(); + } + + /** + * This method is an overloaded method for getting the expanded name namespaceURI followed by the local name for + * Attr + * + * @param attribute + * @return Returns the expanded name of the Attr + */ + public String getExpandedName(Attr attribute) { + return attribute.getNamespaceURI() + ":" + attribute.getLocalName(); + } + + /** + * Gets the collection of attributes which are none namespace declarations for an Element + * + * @param element + * @return Returns the collection of attributes which are none namespace declarations + */ + public Collection getAttributesWithoutNS(Element element) { + SortedMap map = new TreeMap(); + for (int i = 0; i < element.getAttributes().getLength(); i++) { + Attr attribute = (Attr) element.getAttributes().item(i); + if (!(attribute.getLocalName().equals("xmlns") || attribute + .getLocalName().startsWith("xmlns:"))) + map.put(getExpandedName(attribute), attribute); + } + return map.values(); + } + + /** + * Gets the valid element collection of an Document. Element and ProcessingInstruction only + * + * @param document + * @return Returns a collection of ProcessingInstructions and Elements + */ + public Collection getValidElements(Document document) { + ArrayList list = new ArrayList(); + NodeList childNodes = document.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE + || node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) + list.add(node); + } + return list; + } + + /** + * Gets the String representation of the byte array + * + * @param array + * @return Returns the String of the byte + */ + public String getStringRepresentation(byte[] array) { + String str = ""; + for (int i = 0; i < array.length; i++) + str += array[i]; + return str; + } + + /** + * Compares two Nodes for the XML equality + * + * @param node + * @param comparingNode + * @param digestAlgorithm + * @return Returns true if the Node XML contents are equal + */ + public boolean compareNode(Node node, Node comparingNode, + String digestAlgorithm) throws Exception { + return Arrays.equals(getDigest(node, digestAlgorithm), getDigest( + comparingNode, digestAlgorithm)); + } + + /** + * Compares two Documents for the XML equality + * + * @param document + * @param comparingDocument + * @param digestAlgorithm + * @return Returns true if the Document XML content are equal + */ + public boolean compareDocument(Document document, + Document comparingDocument, String digestAlgorithm) + throws Exception { + return Arrays.equals(getDigest(document, digestAlgorithm), getDigest( + comparingDocument, digestAlgorithm)); + } + + /** + * Compares two Attributes for the XML equality + * + * @param attribute + * @param comparingAttribute + * @param digestAlgorithm + * @return Returns true if the Document XML content are equal + */ + public boolean compareAttribute(Attr attribute, Attr comparingAttribute, + String digestAlgorithm) throws Exception { + return Arrays.equals(getDigest(attribute, digestAlgorithm), getDigest( + comparingAttribute, digestAlgorithm)); + } + + /** String representing the MD5 digest algorithm */ + public static final String md5DigestAlgorithm = "MD5"; + + /** String representing the SHA digest algorithm */ + public static final String shaDigestAlgorithm = "SHA"; + + /** String representing the SHA1 digest algorithm */ + public static final String sha1DigestAlgorithm = "SHA1"; +}