/* * Copyright 2017 TNG Technology Consulting GmbH * * 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.tngtech.archunit.core.domain; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Objects; import com.google.common.io.ByteStreams; import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.Optional; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; /** * Contains information about an imported class, i.e. the URI from where the class was imported and an md5 sum * to compare different versions of the same class file at the same location. * <p> * <b>NOTE</b>: Since the generation of md5 sums has a performance impact, it is disabled by default.<br> * To enable it, add * <br><br><code> * {@value com.tngtech.archunit.ArchConfiguration#ENABLE_MD5_IN_CLASS_SOURCES}=true * </code><br><br> * to your <code>{@value com.tngtech.archunit.ArchConfiguration#ARCHUNIT_PROPERTIES_RESOURCE_NAME}</code>. * </p> */ public class Source { private final URI uri; private final Md5sum md5sum; Source(URI uri) { this.uri = uri; md5sum = Md5sum.of(uri); } @PublicAPI(usage = ACCESS) public URI getUri() { return uri; } @PublicAPI(usage = ACCESS) public Md5sum getMd5sum() { return md5sum; } @Override public int hashCode() { return Objects.hash(uri, md5sum); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final Source other = (Source) obj; return Objects.equals(this.uri, other.uri) && Objects.equals(this.md5sum, other.md5sum); } @Override public String toString() { return uri + " [md5='" + md5sum + "']"; } public static class Md5sum { /** * We can't determine the md5 sum, because the platform is missing the digest algorithm */ static final Md5sum NOT_SUPPORTED = new Md5sum("NOT_SUPPORTED"); /** * We can't determine the md5 sum, due to an error while digesting the source */ static final Md5sum UNDETERMINED = new Md5sum("UNDETERMINED"); /** * The calculation of md5 sums is disabled via {@link ArchConfiguration} */ static final Md5sum DISABLED = new Md5sum("DISABLED"); private static final MessageDigest MD5_DIGEST = getMd5Digest(); private final byte[] md5Bytes; private final String text; private Md5sum(String text) { this.md5Bytes = new byte[0]; this.text = text; } private Md5sum(byte[] input, MessageDigest md5Digest) { this.md5Bytes = md5Digest.digest(input); text = toHex(md5Bytes); } static String toHex(byte[] bytes) { StringBuilder sb = new StringBuilder(2 * bytes.length); for (byte b : bytes) { sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]); } return sb.toString(); } private static final char[] hexDigits = "0123456789abcdef".toCharArray(); @PublicAPI(usage = ACCESS) public byte[] asBytes() { return Arrays.copyOf(md5Bytes, md5Bytes.length); } @Override public int hashCode() { return Arrays.hashCode(md5Bytes) + 31 * text.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final Md5sum other = (Md5sum) obj; return Arrays.equals(this.md5Bytes, other.md5Bytes) && Objects.equals(this.text, other.text); } @Override public String toString() { return text; } private static MessageDigest getMd5Digest() { try { return MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { return null; } } static Md5sum of(byte[] input) { if (MD5_DIGEST == null) { return NOT_SUPPORTED; } return ArchConfiguration.get().md5InClassSourcesEnabled() ? new Md5sum(input, MD5_DIGEST) : DISABLED; } private static Md5sum of(URI uri) { if (!ArchConfiguration.get().md5InClassSourcesEnabled()) { return DISABLED; } Optional<byte[]> bytesFromUri = read(uri); return bytesFromUri.isPresent() ? Md5sum.of(bytesFromUri.get()) : UNDETERMINED; } private static Optional<byte[]> read(URI uri) { try { return Optional.of(ByteStreams.toByteArray(uri.toURL().openStream())); } catch (Exception e) { return Optional.absent(); } } } }