/*
* Copyright 2013 NGDATA nv
*
* 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.lilyproject.repository.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.joda.time.DateTime;
import org.lilyproject.bytes.api.ByteArray;
/**
* Builder for {@link Metadata} objects.
*
* <p>The way this works is that you instantiate a MetadataBuilder, call a few of its {@link #value(String, String)}
* methods, and finally call {@link #build()} to create a Metadata object</p>
*
* <p>An example:</p>
*
* <pre>
* Metadata metadata = new MetadataBuilder()
* .value("field1", "value1")
* .value("field2", 5)
* .build();
* </pre>
*/
public class MetadataBuilder {
private Map<String, Object> data = new HashMap<String, Object>();
private Set<String> fieldsToDelete;
/**
* Copies all information from the provided metadata object, both fields and fields-to-delete.
*
* <p>The metadata can be null, in which case this method will silently do nothing. This allows
* for convenient modification of a field's metadata like this:</p>
*
* <pre>
* record.setMetadata(fieldName, new MetadataBuilder()
* .from(record.getMetadata(fieldName))
* .value("foo", "bar")
* .build());
* </pre>
*/
public MetadataBuilder from(Metadata metadata) {
if (metadata == null)
return this;
for (Map.Entry<String, Object> entry : metadata.getMap().entrySet()) {
object(entry.getKey(), entry.getValue());
}
for (String field : metadata.getFieldsToDelete()) {
delete(field);
}
return this;
}
public MetadataBuilder value(String key, String value) {
if (value == null) {
delete(key);
} else {
data.put(key, value);
removeFromFieldsToDelete(key);
}
return this;
}
/**
* Generic setter for all types of values.
*
* <p>The actual types of values supported are still only those for which there are
* specific setters as well.</p>
*/
public MetadataBuilder object(String key, Object value) {
if (value == null) {
delete(key);
} else {
data.put(key, value);
removeFromFieldsToDelete(key);
}
return this;
}
public MetadataBuilder value(String key, int value) {
data.put(key, value);
removeFromFieldsToDelete(key);
return this;
}
public MetadataBuilder value(String key, long value) {
data.put(key, value);
removeFromFieldsToDelete(key);
return this;
}
public MetadataBuilder value(String key, boolean value) {
data.put(key, value);
removeFromFieldsToDelete(key);
return this;
}
public MetadataBuilder value(String key, float value) {
data.put(key, value);
removeFromFieldsToDelete(key);
return this;
}
public MetadataBuilder value(String key, double value) {
data.put(key, value);
removeFromFieldsToDelete(key);
return this;
}
public MetadataBuilder value(String key, ByteArray value) {
if (value == null) {
delete(key);
} else {
data.put(key, value);
removeFromFieldsToDelete(key);
}
return this;
}
public MetadataBuilder value(String key, DateTime value) {
if (value == null) {
delete(key);
} else {
data.put(key, value);
removeFromFieldsToDelete(key);
}
return this;
}
/**
* Explicitly deletes a metadata value, adding it to the list of fields to delete.
*/
public MetadataBuilder delete(String key) {
data.remove(key);
if (fieldsToDelete == null) {
fieldsToDelete = new HashSet<String>();
}
fieldsToDelete.add(key);
return this;
}
private void removeFromFieldsToDelete(String field) {
if (fieldsToDelete != null) {
fieldsToDelete.remove(field);
}
}
public Metadata build() {
// because builders could be reused, and we want metadata to be immutable, the map & set are cloned,
// though I don't like the extra work & garbage this creates (alternatives would be to make builders
// non-reusable or to let the build method also do a reset of the builder state)
return new Metadata(new HashMap<String, Object>(data),
fieldsToDelete == null ? Collections.<String>emptySet() : new HashSet<String>(fieldsToDelete));
}
}