/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* 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.speedment.runtime.config;
import com.speedment.common.function.OptionalBoolean;
import com.speedment.runtime.config.util.DocumentUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.function.BiFunction;
import java.util.stream.Stream;
/**
* The {@code Document} is the base structure for storing configuration
* parameters and can be seen as a hierarchial key-value store. Parameters can
* be retreived using the different typed {@code getAsXXX()} methods listed
* here:
* <ul>
* <li>{@link #get(String)}
* <li>{@link #getAsBoolean(String)}
* <li>{@link #getAsLong(String)}
* <li>{@link #getAsDouble(String)}
* <li>{@link #getAsInt(String)}
* <li>{@link #getAsString(String)}
* </ul>
* <p>
* To traverse the document tree, the {@link #children(String, BiFunction)}
* method can be used. It takes a key and a constructor method reference and
* returns a stream of constructed children.
* <p>
* {@code Document} instances are typically short-lived objects that are
* recreated every time they are used. References to other instances than the
* root should therefore not be stored outside the tree.
*
* @author Emil Forsund
* @since 2.3.0
*/
public interface Document {
/**
* Returns the parent of this Document or {@link Optional#empty()} if the
* Document does not have a parent.
*
* @return the parent
*/
Optional<? extends Document> getParent();
/**
* Returns the raw data-map used in this {@code Document}.
*
* @return the raw map
*/
Map<String, Object> getData();
/**
* Returns the value mapped to the specified key, or an empty
* {@code Optional} if no value was found.
*
* @param key the key
* @return the mapped value or an empty {@code Optional}
*/
Optional<Object> get(String key);
/**
* Returns the {@code boolean} mapped to the specified key, or an empty
* {@code OptionalBoolean} if no value was found. If a value was found but
* couldn't be parsed into a {@code boolean}, a {@code ClassCastException}
* will be thrown.
*
* @param key the key
* @return the mapped value or an empty {@code OptionalBoolean}
* @throws ClassCastException if the mapped value was not a {@code Boolean}
*/
OptionalBoolean getAsBoolean(String key) throws ClassCastException;
/**
* Returns the {@code long} mapped to the specified key, or an empty
* {@code OptionalLong} if no value was found. If a value was found but
* couldn't be parsed into a {@code long}, a {@code ClassCastException}
* will be thrown.
*
* @param key the key
* @return the mapped value or an empty {@code OptionalLong}
* @throws ClassCastException if the mapped value was not a {@code Long}
*/
OptionalLong getAsLong(String key) throws ClassCastException;
/**
* Returns the {@code double} mapped to the specified key, or an empty
* {@code OptionalDouble} if no value was found. If a value was found but
* couldn't be parsed into a {@code double}, a {@code ClassCastException}
* will be thrown.
*
* @param key the key
* @return the mapped value or an empty {@code OptionalDouble}
* @throws ClassCastException if the mapped value was not a {@code Double}
*/
OptionalDouble getAsDouble(String key) throws ClassCastException;
/**
* Returns the {@code int} mapped to the specified key, or an empty
* {@code OptionalInt} if no value was found. If a value was found but
* couldn't be parsed into a {@code int}, a {@code ClassCastException}
* will be thrown.
*
* @param key the key
* @return the mapped value or an empty {@code OptionalInt}
* @throws ClassCastException if the mapped value was not a {@code Integer}
*/
OptionalInt getAsInt(String key) throws ClassCastException;
/**
* Returns the {@code String} mapped to the specified key, or an empty
* {@code Optional} if no value was found. If a value was found but
* couldn't be parsed into a {@code String}, a {@code ClassCastException}
* will be thrown.
*
* @param key the key
* @return the mapped value or an empty {@code Optional}
* @throws ClassCastException if the mapped value was not a {@code String}
*/
Optional<String> getAsString(String key) throws ClassCastException;
/**
* Stores the specified value on the specified key in this document. If a
* value was already mapped to that key, it will be overwritten.
*
* @param key the key to store the value on
* @param value the value to store
*/
void put(String key, Object value);
/**
* Returns the children on the specified key, instantiated using the
* specified constructor. This instance will be used as parent to the
* constructor.
*
* @param <P> the parent type
* @param <T> the child type
* @param key the key
* @param constructor the child constructor to use
* @return a stream of constructed children
*
* @see #children()
*/
default <P extends Document, T extends Document> Stream<T> children(
String key, BiFunction<P, Map<String, Object>, T> constructor) {
final List<Map<String, Object>> list = get(key)
.map(DocumentUtil::castToDocumentList)
.orElse(null);
if (list == null) {
return Stream.empty();
} else {
@SuppressWarnings("unchecked")
final P thizz = (P)this;
return list.stream().map(map -> constructor.apply(thizz, map));
}
}
/**
* Returns a {@code Stream} of child documents, instantiated using the
* default document implementation. This method will consider every value
* that is a {@code List} of {@code Map} instances a child.
*
* @return a stream of every child
* @see #children(String, BiFunction)
*/
Stream<? extends Document> children();
/**
* Returns a {@code Stream} of every ancestor to this {@code Document},
* beginning with the first ancestor (the root) and ending with the parent
* of this {@code Document}.
* <p>
* This stream can typically not be short-circuited and operations on it
* will therefore have a complexity of {@code O(n)}.
*
* @return a stream of ancestors, from the root to the parent
*/
default Stream<Document> ancestors() {
final List<Document> ancestors = new ArrayList<>();
Document parent = this;
while ((parent = parent.getParent().orElse(null)) != null) {
ancestors.add(parent);
}
Collections.reverse(ancestors);
return ancestors.stream();
}
}