/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.hawkular.inventory.api.model;
import static java.util.stream.Collectors.toList;
import static org.hawkular.inventory.paths.DataRole.OperationType.parameterTypes;
import static org.hawkular.inventory.paths.DataRole.OperationType.returnType;
import static org.hawkular.inventory.paths.DataRole.Resource.configuration;
import static org.hawkular.inventory.paths.DataRole.Resource.connectionConfiguration;
import static org.hawkular.inventory.paths.DataRole.ResourceType.configurationSchema;
import static org.hawkular.inventory.paths.DataRole.ResourceType.connectionConfigurationSchema;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.hawkular.inventory.api.Inventory;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.DataRole;
import org.hawkular.inventory.paths.Path;
import org.hawkular.inventory.paths.RelativePath;
import org.hawkular.inventory.paths.SegmentType;
/**
* Contains common utilities for computing the different kinds of hashes.
*
* @author Lukas Krejci
* @since 0.18.0
*/
final class ComputeHash {
static final Comparator<Entity<?, ?>> ENTITY_COMPARATOR = (a, b) -> {
if (a == null) return b == null ? 0 : -1;
if (b == null) return 1;
if (!a.getClass().equals(b.getClass())) {
return a.getClass().getName().compareTo(b.getClass().getName());
} else {
return a.getId().compareTo(b.getId());
}
};
static final Comparator<Entity.Blueprint> BLUEPRINT_COMPARATOR = (a, b) -> {
if (a == null) return b == null ? 0 : -1;
if (b == null) return 1;
Class<? extends Entity<Entity.Blueprint, ?>> ac = Blueprint.getEntityTypeOf(a);
Class<? extends Entity<Entity.Blueprint, ?>> bc = Blueprint.getEntityTypeOf(b);
if (!ac.equals(bc)) {
return ac.getName().compareTo(bc.getName());
} else {
return a.getId().compareTo(b.getId());
}
};
private ComputeHash() {
}
/**
* @return a new message digest to use for hash computation.
*/
static MessageDigest newDigest() {
try {
return MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Could not instantiate SHA-1 digest algorithm.", e);
}
}
private static <R extends DataRole> DataEntity.Blueprint<R> dummyDataBlueprint(R role) {
return DataEntity.Blueprint.<R>builder().withRole(role).withValue(StructuredData.get().undefined()).build();
}
static Hashes of(InventoryStructure<?> inventory, CanonicalPath rootPath, boolean computeIdentity,
boolean computeContent, boolean computeSync) {
ComputeHash.HashConstructor ctor = new ComputeHash.HashConstructor(new ComputeHash.DigestComputingWriter(
ComputeHash.newDigest()));
IntermediateHashResult res =
ComputeHash.computeHash(rootPath, inventory.getRoot(), ComputeHash.HashableView.of(inventory), ctor,
computeIdentity, computeContent, computeSync, (rp) -> null);
return new Hashes(res.identityHash, res.contentHash, res.syncHash);
}
static IntermediateHashResult treeOf(InventoryStructure<?> inventory, CanonicalPath rootPath,
boolean computeIdentity,
boolean computeContent, boolean computeSync,
Consumer<IntermediateHashContext> onStartChild,
BiConsumer<IntermediateHashContext, IntermediateHashResult> onEndChild) {
ComputeHash.DigestComputingWriter wrt = new ComputeHash.DigestComputingWriter(ComputeHash.newDigest());
ComputeHash.HashConstructor ctor = new ComputeHash.HashConstructor(wrt) {
@Override public void startChild(ComputeHash.IntermediateHashContext context) {
super.startChild(context);
onStartChild.accept(context);
}
@Override
public void endChild(ComputeHash.IntermediateHashContext ctx, ComputeHash.IntermediateHashResult result) {
super.endChild(ctx, result);
onEndChild.accept(ctx, result);
}
};
return computeHash(rootPath, inventory.getRoot(), ComputeHash.HashableView.of(inventory), ctor, computeIdentity,
computeContent, computeSync,
//we don't want the root element in the relative paths of the children so that they are easily
//appendable to the root.
(rp) -> rp.slide(1, 0)
);
}
static IntermediateHashResult computeHash(CanonicalPath entityPath, Blueprint entity, HashableView structure,
HashConstructor bld, boolean compIdentity, boolean compContent,
boolean compSync, Function<RelativePath, RelativePath> pathCompleter) {
Class<?> entityType = Inventory.types().byBlueprint(entity.getClass()).getElementType();
boolean syncable = Syncable.class.isAssignableFrom(entityType);
boolean identityHashable = IdentityHashable.class.isAssignableFrom(entityType);
boolean contentHashable = ContentHashable.class.isAssignableFrom(entityType);
compIdentity = compIdentity || compSync;
compContent = compContent || compSync;
boolean computeIdentity = compIdentity && identityHashable;
boolean computeContent = compContent && contentHashable;
boolean computeSync = compSync && syncable;
return entity.accept(new ElementBlueprintVisitor.Simple<IntermediateHashResult, IntermediateHashContext>() {
@Override
public IntermediateHashResult visitData(DataEntity.Blueprint<?> data, IntermediateHashContext ctx) {
return wrap(data, ctx, (childContext) -> {
try {
StringBuilder json = new StringBuilder();
data.getValue().writeJSON(json);
if (computeIdentity) {
appendIdentity(data.getId(), childContext);
appendIdentity(json.toString(), childContext);
}
if (computeContent) {
appendContent(json.toString(), childContext);
appendCommonContent(data, childContext);
}
if (computeSync) {
appendCommonSync(data, childContext);
}
} catch (IOException e) {
throw new IllegalStateException("Could not write out JSON for hash computation purposes.", e);
}
});
}
@Override
public IntermediateHashResult visitMetricType(MetricType.Blueprint mt, IntermediateHashContext ctx) {
return wrap(mt, ctx, (childContext) -> {
if (computeIdentity) {
appendIdentity(mt.getId(), childContext);
appendIdentity(mt.getMetricDataType().name(), childContext);
appendIdentity(mt.getUnit().name(), childContext);
}
if (computeContent) {
appendContent(mt.getMetricDataType().name(), childContext);
appendContent(mt.getUnit().name(), childContext);
appendContent(Objects.toString(mt.getCollectionInterval(), ""), childContext);
appendCommonContent(mt, childContext);
}
if (computeSync) {
appendCommonSync(mt, childContext);
}
});
}
@Override
public IntermediateHashResult visitOperationType(OperationType.Blueprint operationType,
IntermediateHashContext ctx) {
return wrap(operationType, ctx, (childContext) -> {
if (computeIdentity) {
appendEntityIdentity(structure.getReturnType(ctx.root, operationType), childContext);
appendEntityIdentity(structure.getParameterTypes(ctx.root, operationType), childContext);
appendIdentity(operationType.getId(), childContext);
}
if (computeContent) {
appendCommonContent(operationType, childContext);
}
if (computeSync) {
appendCommonSync(operationType, childContext);
}
});
}
@Override
public IntermediateHashResult visitResourceType(ResourceType.Blueprint type,
IntermediateHashContext ctx) {
return wrap(type, ctx, (childContext) -> {
if (computeIdentity) {
appendEntityIdentity(structure.getConfigurationSchema(type), childContext);
appendEntityIdentity(structure.getConnectionConfigurationSchema(type), childContext);
structure.getOperationTypes(type).forEach(b -> appendEntityIdentity(b, childContext));
appendIdentity(type.getId(), childContext);
}
if (computeContent) {
appendCommonContent(type, childContext);
}
if (computeSync) {
appendCommonSync(type, childContext);
}
});
}
@Override
public IntermediateHashResult visitFeed(Feed.Blueprint feed, IntermediateHashContext ctx) {
return wrap(feed, ctx, (childContext) -> {
if (computeIdentity) {
structure.getResourceTypes().forEach(b -> appendEntityIdentity(b, childContext));
structure.getMetricTypes().forEach(b -> appendEntityIdentity(b, childContext));
structure.getFeedResources().forEach(b -> appendEntityIdentity(b, childContext));
structure.getFeedMetrics().forEach(b -> appendEntityIdentity(b, childContext));
appendIdentity(feed.getId(), childContext);
}
if (computeContent) {
appendCommonContent(feed, childContext);
}
if (computeSync) {
appendCommonSync(feed, childContext);
}
});
}
@Override
public IntermediateHashResult visitMetric(Metric.Blueprint metric, IntermediateHashContext ctx) {
return wrap(metric, ctx, (childContext) -> {
if (computeIdentity) {
appendIdentity(metric.getId(), childContext);
}
if (computeContent) {
RelativePath mtPath = relativize(metric.getMetricTypePath(), SegmentType.mt, childContext);
appendContent(mtPath.toString(), childContext);
appendContent(Objects.toString(metric.getCollectionInterval(), ""), childContext);
appendCommonContent(metric, childContext);
}
if (computeSync) {
appendCommonSync(metric, childContext);
}
});
}
@Override
public IntermediateHashResult visitResource(Resource.Blueprint resource,
IntermediateHashContext context) {
return wrap(resource, context, (childContext) -> {
if (computeIdentity) {
appendEntityIdentity(structure.getConfiguration(context.root, resource), childContext);
appendEntityIdentity(structure.getConnectionConfiguration(context.root, resource),
childContext);
structure.getResources(context.root, resource)
.forEach(b -> appendEntityIdentity(b, childContext));
structure.getResourceMetrics(context.root, resource)
.forEach(b -> appendEntityIdentity(b, childContext));
appendIdentity(resource.getId(), childContext);
}
if (computeContent) {
RelativePath rtPath = relativize(resource.getResourceTypePath(), SegmentType.rt, childContext);
appendContent(rtPath.toString(), childContext);
appendCommonContent(resource, childContext);
}
if (computeSync) {
appendCommonSync(resource, childContext);
}
});
}
@Override
public IntermediateHashResult visitTenant(Tenant.Blueprint tenant, IntermediateHashContext context) {
return wrap(tenant, context, childContext -> {
if (computeContent) {
appendCommonContent(tenant, childContext);
}
});
}
@Override
public IntermediateHashResult visitEnvironment(Environment.Blueprint environment,
IntermediateHashContext context) {
return wrap(environment, context, childContext -> {
if (computeContent) {
appendCommonContent(environment, childContext);
}
});
}
private RelativePath relativize(String relativePath, SegmentType targetType, IntermediateHashContext ctx) {
Path targetPath = Path.fromPartiallyUntypedString(relativePath,
CanonicalPath.of().tenant(entityPath.ids().getTenantId()).get(), entityPath,
targetType);
if (targetPath.isCanonical()) {
targetPath = targetPath.toCanonicalPath().relativeTo(entityPath);
}
return targetPath.toRelativePath();
}
private IntermediateHashResult wrap(Entity.Blueprint root, IntermediateHashContext context,
Consumer<IntermediateHashContext> hashComputation) {
IntermediateHashContext childCtx = context.progress(root);
bld.startChild(childCtx);
hashComputation.accept(childCtx);
String identityHash = null;
String contentHash = null;
String syncHash = null;
DigestComputingWriter digestor = bld.getDigestor();
if (computeIdentity) {
digestor.reset();
digestor.append(childCtx.identity);
digestor.close();
identityHash = digestor.digest();
}
if (computeContent) {
digestor.reset();
digestor.append(childCtx.content);
digestor.close();
contentHash = digestor.digest();
}
if (computeSync) {
digestor.reset();
digestor.append(identityHash);
digestor.append(contentHash);
digestor.append(childCtx.sync);
digestor.close();
syncHash = digestor.digest();
}
IntermediateHashResult ret;
if (pathCompleter == null) {
ret = new IntermediateHashResult(null, identityHash, contentHash, syncHash);
} else {
ret = new IntermediateHashResult(pathCompleter.apply(childCtx.root), identityHash, contentHash,
syncHash);
}
bld.endChild(childCtx, ret);
return ret;
}
private void appendEntityIdentity(Entity.Blueprint child, IntermediateHashContext ctx) {
ctx.identity.append(child.accept(this, ctx).identityHash);
}
}, new IntermediateHashContext(RelativePath.empty().get()));
}
static void appendIdentity(String data, IntermediateHashContext ctx) {
if (data != null) {
ctx.identity.append(data);
}
}
static void appendContent(String data, IntermediateHashContext ctx) {
if (data != null) {
ctx.content.append(data);
}
}
static void appendContent(Map<String, Object> props, IntermediateHashContext ctx) {
if (props == null) {
return;
}
SortedMap<String, Object> sorted = new TreeMap<>(Comparator.naturalOrder());
sorted.putAll(props);
for (Map.Entry<String, Object> e : sorted.entrySet()) {
ctx.content.append(e.getKey()).append(e.getValue());
}
}
static void appendCommonContent(Entity.Blueprint b, IntermediateHashContext ctx) {
appendContent(b.getName(), ctx);
appendContent(b.getProperties(), ctx);
}
static void appendSync(String data, IntermediateHashContext ctx) {
if (data != null) {
ctx.sync.append(data);
}
}
static void appendCommonSync(Entity.Blueprint bl, IntermediateHashContext ctx) {
//The code below would cause relationships to influence the sync hash... After some consideration, this is not
//advisable because it would imply that sync will also synchronize the relationships going in/out of entities. This
//should not be done, though, because it would erase all the possible custom relationships that were defined out of
//control and unbeknownst to the sync-caller (i.e. the agent).
// Comparator<Map.Entry<String, List<String>>> sortRelationships = (a, b) -> {
// int names = a.getKey().compareTo(b.getKey());
// if (names != 0) {
// return names;
// }
//
// List<String> aPaths = a.getValue();
// List<String> bPaths = b.getValue();
//
// int lenDiff = aPaths.size() - bPaths.size();
// if (lenDiff != 0) {
// return lenDiff;
// }
//
// for (int i = 0; i < aPaths.size(); ++i) {
// String ap = aPaths.get(i);
// String bp = bPaths.get(i);
//
// int comp = ap.compareTo(bp);
//
// if (comp != 0) {
// return comp;
// }
// }
//
// return 0;
// };
//
// Function<Map.Entry<String, Set<CanonicalPath>>, Map.Entry<String, List<String>>> sortTargets =
// e -> new AbstractMap.SimpleEntry<>(e.getKey(),
// e.getValue().stream().map(Object::toString).sorted().collect(toList()));
//
// Consumer<Map.Entry<String, List<String>>> append = e -> {
// appendSync(e.getKey(), ctx);
// e.getValue().forEach(p -> appendSync(p, ctx));
// };
//
// bl.getIncomingRelationships().entrySet().stream()
// .map(sortTargets)
// .sorted(sortRelationships)
// .forEach(append);
//
// bl.getOutgoingRelationships().entrySet().stream()
// .map(sortTargets)
// .sorted(sortRelationships)
// .forEach(append);
}
interface HashableView {
static HashableView of(MetadataPack.Members members) {
return new HashableView() {
@Override public List<ResourceType.Blueprint> getResourceTypes() {
return members.getResourceTypes();
}
@Override public List<MetricType.Blueprint> getMetricTypes() {
return members.getMetricTypes();
}
@Override public List<OperationType.Blueprint> getOperationTypes(ResourceType.Blueprint resourceType) {
List<OperationType.Blueprint> ret = new ArrayList<>(members.getOperationTypes(resourceType));
Collections.sort(ret, BLUEPRINT_COMPARATOR);
return ret;
}
@Override public DataEntity.Blueprint<?> getReturnType(RelativePath resourceType,
OperationType.Blueprint operationType) {
return members.getReturnType(operationType);
}
@Override public DataEntity.Blueprint<?> getParameterTypes(RelativePath resourceType,
OperationType.Blueprint operationType) {
return members.getParameterTypes(operationType);
}
@Override public DataEntity.Blueprint<?> getConfigurationSchema(ResourceType.Blueprint rt) {
return members.getConfigurationSchema(rt);
}
@Override public DataEntity.Blueprint<?> getConnectionConfigurationSchema(ResourceType.Blueprint rt) {
return members.getConnectionConfigurationSchema(rt);
}
};
}
static HashableView of(InventoryStructure<?> structure) {
return new HashableView() {
@Override
public DataEntity.Blueprint<?> getConfiguration(RelativePath rootPath,
Resource.Blueprint parentResource) {
RelativePath resourcePath = rootPath.modified().extend(SegmentType.r, parentResource.getId())
.get().slide(1, 0);
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(resourcePath, DataEntity.class)) {
return s.filter(d -> configuration.equals(d.getRole()))
.findFirst().orElse(dummyDataBlueprint(configuration));
}
}
@Override public DataEntity.Blueprint<?> getConfigurationSchema(ResourceType.Blueprint rt) {
RelativePath p = rt.equals(structure.getRoot()) ? RelativePath.empty().get()
: RelativePath.to().resourceType(rt.getId()).get();
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(p, DataEntity.class)
.filter(d -> configurationSchema.equals(d.getRole()))) {
return s.findFirst().orElse(dummyDataBlueprint(configurationSchema));
}
}
@Override
public DataEntity.Blueprint<?> getConnectionConfiguration(RelativePath root,
Resource.Blueprint parentResource) {
RelativePath resourcePath = root.modified().extend(SegmentType.r, parentResource.getId())
.get().slide(1, 0);
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(resourcePath, DataEntity.class)
.filter(d -> connectionConfiguration.equals(d.getRole()))) {
return s.findFirst().orElse(dummyDataBlueprint(connectionConfiguration));
}
}
@Override public DataEntity.Blueprint<?> getConnectionConfigurationSchema(ResourceType.Blueprint rt) {
RelativePath p = rt.equals(structure.getRoot()) ? RelativePath.empty().get()
: RelativePath.to().resourceType(rt.getId()).get();
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(p, DataEntity.class)
.filter(d -> connectionConfigurationSchema.equals(d.getRole()))) {
return s.findFirst().orElse(dummyDataBlueprint(connectionConfigurationSchema));
}
}
@Override public List<Metric.Blueprint> getFeedMetrics() {
try (Stream<Metric.Blueprint> s = structure.getChildren(RelativePath.empty().get(), Metric.class)) {
return s.collect(toList());
}
}
@Override public List<Resource.Blueprint> getFeedResources() {
try (Stream<Resource.Blueprint> s = structure.getChildren(RelativePath.empty().get(),
Resource.class)) {
return s.collect(toList());
}
}
@Override public List<MetricType.Blueprint> getMetricTypes() {
try (Stream<MetricType.Blueprint> s = structure.getChildren(RelativePath.empty().get(),
MetricType.class)) {
return s.collect(toList());
}
}
@Override public List<OperationType.Blueprint> getOperationTypes(ResourceType.Blueprint rt) {
RelativePath p = rt.equals(structure.getRoot()) ? RelativePath.empty().get()
: RelativePath.to().resourceType(rt.getId()).get();
try (Stream<OperationType.Blueprint> s = structure.getChildren(p, OperationType.class)) {
return s.collect(toList());
}
}
@Override
public DataEntity.Blueprint<?> getParameterTypes(RelativePath rootResourceType,
OperationType.Blueprint ot) {
RelativePath p = rootResourceType.modified().extend(SegmentType.ot, ot.getId()).get()
.slide(1, 0);
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(p, DataEntity.class)
.filter(d -> parameterTypes.equals(d.getRole()))) {
return s.findFirst().orElse(dummyDataBlueprint(parameterTypes));
}
}
@Override
public List<Metric.Blueprint> getResourceMetrics(RelativePath rootPath,
Resource.Blueprint parentResource) {
RelativePath p = rootPath.modified().extend(SegmentType.r, parentResource.getId()).get()
.slide(1, 0);
try (Stream<Metric.Blueprint> s = structure.getChildren(p, Metric.class)) {
return s.collect(toList());
}
}
@Override
public List<Resource.Blueprint> getResources(RelativePath rootPath, Resource.Blueprint parentResource) {
RelativePath p = rootPath.modified().extend(SegmentType.r, parentResource.getId()).get()
.slide(1, 0);
try (Stream<Resource.Blueprint> s = structure.getChildren(p, Resource.class)) {
return s.collect(toList());
}
}
@Override public List<ResourceType.Blueprint> getResourceTypes() {
try (Stream<ResourceType.Blueprint> s = structure.getChildren(RelativePath.empty().get(),
ResourceType.class)) {
return s.collect(toList());
}
}
@Override public DataEntity.Blueprint<?> getReturnType(RelativePath rootResourceType,
OperationType.Blueprint ot) {
RelativePath p = rootResourceType.modified().extend(SegmentType.ot, ot.getId()).get()
.slide(1, 0);
try (Stream<DataEntity.Blueprint<?>> s = structure.getChildren(p, DataEntity.class)
.filter(d -> returnType.equals(d.getRole()))) {
return s.findFirst().orElse(dummyDataBlueprint(returnType));
}
}
};
}
default List<ResourceType.Blueprint> getResourceTypes() {
return Collections.emptyList();
}
default List<MetricType.Blueprint> getMetricTypes() {
return Collections.emptyList();
}
default List<OperationType.Blueprint> getOperationTypes(ResourceType.Blueprint rt) {
return Collections.emptyList();
}
default DataEntity.Blueprint<?> getReturnType(RelativePath rootResourceType, OperationType.Blueprint ot) {
return dummyDataBlueprint(returnType);
}
default DataEntity.Blueprint<?> getParameterTypes(RelativePath rootResourceType, OperationType.Blueprint ot) {
return dummyDataBlueprint(parameterTypes);
}
default DataEntity.Blueprint<?> getConfigurationSchema(ResourceType.Blueprint rt) {
return dummyDataBlueprint(configurationSchema);
}
default DataEntity.Blueprint<?> getConnectionConfigurationSchema(ResourceType.Blueprint rt) {
return dummyDataBlueprint(connectionConfigurationSchema);
}
default List<Resource.Blueprint> getResources(RelativePath rootPath, Resource.Blueprint parentResource) {
return Collections.emptyList();
}
default List<Resource.Blueprint> getFeedResources() {
return Collections.emptyList();
}
default List<Metric.Blueprint> getFeedMetrics() {
return Collections.emptyList();
}
default List<Metric.Blueprint> getResourceMetrics(RelativePath rootPath, Resource.Blueprint parentResource) {
return Collections.emptyList();
}
default DataEntity.Blueprint<?> getConfiguration(RelativePath rootPath, Resource.Blueprint parentResource) {
return dummyDataBlueprint(configuration);
}
default DataEntity.Blueprint<?> getConnectionConfiguration(RelativePath root, Resource.Blueprint
parentResource) {
return dummyDataBlueprint(connectionConfiguration);
}
}
public static final class Tree {
private Tree() {
}
}
static class DigestComputingWriter implements Appendable, Closeable {
private final MessageDigest digester;
private final CharsetEncoder enc = Charset.forName("UTF-8").newEncoder().onMalformedInput(CodingErrorAction
.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE);
private final ByteBuffer buffer = ByteBuffer.allocate(512);
private String digest;
DigestComputingWriter(MessageDigest digester) {
this.digester = digester;
}
@Override
public DigestComputingWriter append(CharSequence csq) {
consumeBuffer(CharBuffer.wrap(csq));
return this;
}
@Override
public DigestComputingWriter append(CharSequence csq, int start, int end) {
consumeBuffer(CharBuffer.wrap(csq, start, end));
return this;
}
@Override
public DigestComputingWriter append(char c) {
consumeBuffer(CharBuffer.wrap(new char[]{c}));
return this;
}
@Override
public void close() {
this.digest = runningDigest();
}
/**
* Computes the digest of the data appended so far. Notice that this is recomputed every time this method
* is called.
*
* @return the freshly computed digest of the data obtained so far
*/
String runningDigest() {
byte[] digest = digester.digest();
StringBuilder bld = new StringBuilder();
for (byte b : digest) {
bld.append(Integer.toHexString(Byte.toUnsignedInt(b)));
}
return bld.toString();
}
/**
* Obtains the final digest from the entirety of the data. Contrast this with {@link #runningDigest()}.
* <p>This method does not do any computation and is therefore very quick.
* <p>Note that this writer MUST BE {@link #close() CLOSED} for this method to return successfully.
*
* @return the computed digest of the data
*/
String digest() {
if (digest == null) {
throw new IllegalStateException("digest computing writer not closed.");
}
return digest;
}
public void reset() {
digester.reset();
digest = null;
}
private void consumeBuffer(CharBuffer chars) {
CoderResult res;
do {
res = enc.encode(chars, buffer, true);
buffer.flip();
digester.update(buffer);
buffer.clear();
} while (res == CoderResult.OVERFLOW);
}
}
static class IntermediateHashContext {
final RelativePath root;
final StringBuilder identity = new StringBuilder();
final StringBuilder content = new StringBuilder();
final StringBuilder sync = new StringBuilder();
IntermediateHashContext(RelativePath root) {
this.root = root;
}
IntermediateHashContext progress(Entity.Blueprint bl) {
return new IntermediateHashContext(root.modified().extend(Blueprint.getSegmentTypeOf(bl),
bl.getId()).get());
}
}
static class IntermediateHashResult {
final RelativePath path;
final String identityHash;
final String contentHash;
final String syncHash;
private IntermediateHashResult(RelativePath path, String identityHash, String contentHash, String syncHash) {
this.path = path;
this.identityHash = identityHash;
this.contentHash = contentHash;
this.syncHash = syncHash;
}
}
static class HashConstructor {
private final DigestComputingWriter digestor;
HashConstructor(DigestComputingWriter digestor) {
this.digestor = digestor;
}
public DigestComputingWriter getDigestor() {
return digestor;
}
public void startChild(IntermediateHashContext context) {
// System.out.println("start: " + context.root);
}
public void endChild(IntermediateHashContext ctx, IntermediateHashResult result) {
// System.out.println("Intermediate result: path: " + result.path + ", hash: " + result.hash + ", content: "
// + ctx.content);
}
}
}