/*
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.RelativePath;
import org.hawkular.inventory.paths.SegmentType;
/**
* A common super class of both entities and relationships.
*
* @param <B> the blueprint class. The blueprint is used to create a new element.
* @param <U> the update class. The update class is used to update the element.
*
* @author Lukas Krejci
* @since 0.0.1
*/
public abstract class AbstractElement<B extends org.hawkular.inventory.api.model.Blueprint,
U extends AbstractElement.Update> {
public static final String ID_PROPERTY = "id";
private final CanonicalPath path;
protected final Map<String, Object> properties;
/**
* This should be used only in extreme cases where {@link SegmentType} is not possible.
* @param elementType the type of the element
* @return the class representing the element type
*/
public static Class<? extends AbstractElement<?, ?>> toElementClass(SegmentType elementType) {
switch (elementType) {
case t:
return Tenant.class;
case e:
return Environment.class;
case f:
return Feed.class;
case m:
return Metric.class;
case mt:
return MetricType.class;
case r:
return Resource.class;
case rt:
return ResourceType.class;
case rl:
return Relationship.class;
case d:
return DataEntity.class;
case ot:
return OperationType.class;
case mp:
return MetadataPack.class;
default:
throw new IllegalStateException("There is no " + Entity.class.getName() + " type for " +
elementType.getClass().getName() + " '" + elementType.name() + "'");
}
}
//JAXB support
AbstractElement() {
properties = null;
path = null;
}
AbstractElement(CanonicalPath path, Map<String, Object> properties) {
if (properties == null) {
this.properties = null;
} else {
this.properties = new HashMap<>(properties);
this.properties.remove(ID_PROPERTY);
}
this.path = path;
}
/**
* Returns the same result as {@link SegmentType#fromElementType(Class)} but provides a much better performance.
*
* @param cl the type to to map to a {@link SegmentType}
* @return the {@link SegmentType} corresponding to the given {@code cl}
* @throws IllegalStateException if there is no {@link SegmentType} corresponding to the given {@code cl}
*/
public static SegmentType segmentTypeFromType(Class<?> cl) {
if (Tenant.class.equals(cl)) {
return Tenant.SEGMENT_TYPE;
} else if (Environment.class.equals(cl)) {
return Environment.SEGMENT_TYPE;
} else if (Feed.class.equals(cl)) {
return Feed.SEGMENT_TYPE;
} else if (Metric.class.equals(cl)) {
return Metric.SEGMENT_TYPE;
} else if (MetricType.class.equals(cl)) {
return MetricType.SEGMENT_TYPE;
} else if (Resource.class.equals(cl)) {
return Resource.SEGMENT_TYPE;
} else if (ResourceType.class.equals(cl)) {
return ResourceType.SEGMENT_TYPE;
} else if (DataEntity.class.equals(cl)) {
return DataEntity.SEGMENT_TYPE;
} else if (OperationType.class.equals(cl)) {
return OperationType.SEGMENT_TYPE;
} else if (MetadataPack.class.equals(cl)) {
return MetadataPack.SEGMENT_TYPE;
} else if (Relationship.class.equals(cl)) {
return Relationship.SEGMENT_TYPE;
} else if (StructuredData.class.equals(cl)) {
return StructuredData.SEGMENT_TYPE;
} else if (RelativePath.Up.class.equals(cl)) {
return RelativePath.Up.SEGMENT_TYPE;
} else {
throw new IllegalStateException("There is no " + SegmentType.class.getName() + " for type " +
(cl == null ? "null" : cl.getName()));
}
}
/**
* Accepts the provided visitor.
*
* @param visitor the visitor to visit this entity
* @param parameter the parameter to pass on to the visitor
* @param <R> the return type
* @param <P> the type of the parameter
* @return the return value provided by the visitor
*/
public abstract <R, P> R accept(ElementVisitor<R, P> visitor, P parameter);
/**
* @return canonical path to the element
*/
public CanonicalPath getPath() {
return path;
}
/**
* @return the id of the element.
*/
public String getId() {
return path.getSegment().getElementId();
}
/**
* @return a map of arbitrary properties of this entity.
*/
public Map<String, Object> getProperties() {
if (properties == null) {
return Collections.emptyMap();
}
return properties;
}
protected static <T> T valueOrDefault(T value, T defaultValue) {
return value == null ? defaultValue : value;
}
/**
* @return a new updater object to modify this entity and produce a new one.
*/
//if only Java had "Self" type like Rust :(
public abstract Updater<U, ? extends AbstractElement<?, U>> update();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractElement<?, ?> entity = (AbstractElement<?, ?>) o;
return path.equals(entity.path);
}
@Override
public int hashCode() {
return getPath().hashCode();
}
public abstract static class Update {
@SuppressWarnings("unchecked")
static <U extends Update, E extends AbstractElement<?, U>> Class<? extends E> getEntityTypeOf(U update) {
return (Class<? extends E>) (Class) update.getClass().getEnclosingClass();
}
private final Map<String, Object> properties;
public Update(Map<String, Object> properties) {
this.properties = properties;
}
public Map<String, Object> getProperties() {
return properties;
}
public abstract <R, P> R accept(ElementUpdateVisitor<R, P> visitor, P parameter);
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Update)) return false;
Update update = (Update) o;
return properties != null ? properties.equals(update.properties) : update.properties == null;
}
@Override public int hashCode() {
return properties != null ? properties.hashCode() : 0;
}
public abstract static class Builder<E extends AbstractElement<?, U>, U extends Update,
This extends Builder<E, U, This>> {
protected Map<String, Object> properties;
private Map<String, Object> getProperties() {
if (properties == null) {
properties = new HashMap<>();
}
return properties;
}
public This withProperty(String key, Object value) {
getProperties().put(key, value);
return castThis();
}
public This withProperties(Map<String, Object> properties) {
getProperties().putAll(properties);
return castThis();
}
public This withDifference(E original, E updated) {
if (!Objects.equals(original.getProperties(), updated.getProperties())) {
withProperties(updated.getProperties());
}
return castThis();
}
public abstract U build();
@SuppressWarnings("unchecked")
protected This castThis() {
return (This) this;
}
}
}
public static final class Updater<U extends Update, E extends AbstractElement<?, U>> {
private final Function<U, E> updater;
private final E original;
private final Update.Builder<E, U, ?> bld;
Updater(Function<U, E> updater, E original, Update.Builder<E, U, ?> bld) {
this.updater = updater;
this.original = original;
this.bld = bld;
}
public E with(U update) {
return updater.apply(update);
}
public U to(E entity) {
return bld.withDifference(original, entity).build();
}
}
public abstract static class Blueprint implements org.hawkular.inventory.api.model.Blueprint {
private final Map<String, Object> properties;
protected Blueprint(Map<String, Object> properties) {
this.properties = properties;
}
public Map<String, Object> getProperties() {
return properties;
}
public abstract static class Builder<B, This extends Builder<B, This>> {
protected Map<String, Object> properties = new HashMap<>();
public This withProperty(String key, Object value) {
this.properties.put(key, value);
return castThis();
}
public This withProperties(Map<String, Object> properties) {
this.properties.putAll(properties);
return castThis();
}
public abstract B build();
@SuppressWarnings("unchecked")
protected This castThis() {
return (This) this;
}
}
}
}