org.hawkular.inventory.paths.RelativePath Maven / Gradle / Ivy
/*
* Copyright 2014-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.paths;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import io.swagger.annotations.ApiModel;
/**
* A relative path is used in the API to refer to other entities during association. Its precise meaning is
* context-sensitive but the basic idea is that given a position in the graph, you want to refer to other entities that
* are "near" without needing to provide their full canonical path.
*
* I.e. it is quite usual only associate resources and metrics from a single environment. It would be cumbersome to
* require the full canonical path for every metric one wants to associate with a resource. Therefore a partial path is
* used to refer to the metric.
*
*
The relative path contains one special segment type - encoded as ".." and represented using the
* {@link org.hawkular.inventory.paths.RelativePath.Up} class that can be used to go up in the relative path.
*
* @author Lukas Krejci
* @since 0.2.0
*/
@ApiModel
public final class RelativePath extends Path implements Serializable {
static final Map> SHORT_NAME_TYPES = new HashMap<>();
private static final Map> VALID_PROGRESSIONS =
new HashMap<>();
static {
for (SegmentType c : SegmentType.values()) {
EnumSet progressions = CanonicalPath.VALID_PROGRESSIONS.get(c);
if (progressions == null) {
progressions = EnumSet.of(SegmentType.up);
} else {
progressions = EnumSet.of(SegmentType.up, progressions.toArray(new SegmentType[progressions.size()]));
}
VALID_PROGRESSIONS.put(c, progressions);
}
}
RelativePath(int start, int end, List segments) {
super(start, end, segments);
}
public static RelativePath fromString(String path) {
return fromPartiallyUntypedString(path, new StructuredDataHintingTypeProvider());
}
/**
* @param path the relative path to parse
* @param typeProvider the type provider used to figure out types of segments that don't explicitly mention it
* @return the parsed relative path
* @see Path#fromPartiallyUntypedString(String, TypeProvider)
*/
public static RelativePath fromPartiallyUntypedString(String path, TypeProvider typeProvider) {
return (RelativePath) Path.fromString(path, false, Extender::new,
new RelativeTypeProvider(typeProvider));
}
/**
* An overload of {@link #fromPartiallyUntypedString(String, TypeProvider)} which uses the provided initial position
* to figure out the possible type if is missing in the provided relative path.
*
* @param path the relative path to parse
* @param initialPosition the initial position using which the types will be deduced for the segments that don't
* specify the type explicitly
* @param intendedFinalType the type of the final segment in the path. This can resolve potentially ambiguous
* situations where, given the initial position, more choices are possible.
* @return the parsed relative path
*/
public static RelativePath fromPartiallyUntypedString(String path, CanonicalPath initialPosition,
SegmentType intendedFinalType) {
return (RelativePath) Path.fromString(path, false, Extender::new,
new RelativeTypeProvider(new HintedTypeProvider(intendedFinalType,
new RelativePath.Extender(0, new ArrayList<>(initialPosition.getPath())))));
}
/**
* @return an empty canonical path to be extended
*/
public static Extender empty() {
return new Extender(0, new ArrayList<>());
}
public static Builder to() {
return new Builder(new ArrayList<>());
}
@Override
protected Path newInstance(int startIdx, int endIdx, List segments) {
return new RelativePath(startIdx, endIdx, segments);
}
/**
* Applies this relative path on the provided canonical path.
*
* @param path
*/
public CanonicalPath applyTo(CanonicalPath path) {
return toCanonicalPath(new ArrayList<>(path.getPath()));
}
/**
* Tries to convert this relative path to a canonical path. This will only succeed if this relative path truly
* represents a canonical path and thus can be converted to it.
*
* I.e. this will not work on relative paths like {@code ../r;id} which doesn't itself represent a full canonical
* path.
*
* @return a canonical path constructed from this relative path
* @throws IllegalArgumentException if the attempt to convert to canonical path fails
*/
public CanonicalPath toCanonicalPath() {
return toCanonicalPath(new ArrayList<>());
}
public RelativePath toRelativePath() {
return this;
}
private CanonicalPath toCanonicalPath(List startSegments) {
CanonicalPath.Extender extender = new CanonicalPath.Extender(0, startSegments) {
@Override
public CanonicalPath.Extender extend(Segment segment) {
if (SegmentType.up.equals(segment.getElementType())) {
removeLastSegment();
} else {
super.extend(segment);
}
return this;
}
};
getPath().forEach(extender::extend);
return extender.get();
}
@Override
public Extender modified() {
return new Extender(startIdx, new ArrayList<>(path.subList(0, endIdx)));
}
@SuppressWarnings("unchecked")
@Override
public Iterator ascendingIterator() {
return (Iterator) super.ascendingIterator();
}
@SuppressWarnings("unchecked")
@Override
public Iterator descendingIterator() {
return (Iterator) super.descendingIterator();
}
@Override
public RelativePath down() {
return (RelativePath) super.down();
}
@Override
public RelativePath down(int distance) {
return (RelativePath) super.down(distance);
}
@Override
public RelativePath up() {
return (RelativePath) super.up();
}
@Override
public RelativePath up(int distance) {
return (RelativePath) super.up(distance);
}
/**
* Moves the start and end of the path by the provided distances.
*
* Consider the path:
*
{@code a/b/c}
*
* {@code p1 = p.slide(1, 0)} will produce {@code p1 = "b/c"}, {@code p1.slide(-1, 0)} will produce a path
* equivalent to the original one. {@code p.slide(-1, 0)} will produce an undefined path, because it would go past
* the known start of the path (i.e. beyond "a").
*
* @param startDelta the number of steps to move the start of the path
* @param endDelta the number of steps to move the end of the path
* @return a new relative path with modified length and position, possibly undefined
*/
public RelativePath slide(int startDelta, int endDelta) {
return new RelativePath(startIdx + startDelta, endIdx + endDelta, path);
}
public boolean isParentOf(RelativePath other) {
return super.isParentOf(other);
}
@Override
public String toString() {
return new Encoder((s) -> !SegmentType.up.equals(s.getElementType())).encode("", this);
}
public static final class Up {
public static final SegmentType SEGMENT_TYPE = SegmentType.up;
private Up() {
}
}
public static final class Builder extends Path.Builder {
private Builder(List list) {
super(list, RelativePath::new);
}
@Override
protected RelationshipBuilder relationshipBuilder(List list) {
return new RelationshipBuilder(list);
}
@Override
protected TenantBuilder tenantBuilder(List list) {
return new TenantBuilder(list);
}
public EnvironmentBuilder environment(String id) {
segments.add(new Segment(SegmentType.e, id));
return new EnvironmentBuilder(segments);
}
public ResourceTypeBuilder resourceType(String id) {
segments.add(new Segment(SegmentType.rt, id));
return new ResourceTypeBuilder(segments);
}
public MetricTypeBuilder metricType(String id) {
segments.add(new Segment(SegmentType.mt, id));
return new MetricTypeBuilder(segments);
}
public FeedBuilder feed(String id) {
segments.add(new Segment(SegmentType.f, id));
return new FeedBuilder(segments);
}
public ResourceBuilder resource(String id) {
segments.add(new Segment(SegmentType.r, id));
return new ResourceBuilder(segments);
}
public MetricBuilder metric(String id) {
segments.add(new Segment(SegmentType.m, id));
return new MetricBuilder(segments);
}
public StructuredDataBuilder dataEntity(DataRole role) {
segments.add(new Segment(SegmentType.d, role.name()));
return new StructuredDataBuilder(segments);
}
public OperationTypeBuilder operationType(String id) {
segments.add(new Segment(SegmentType.ot, id));
return new OperationTypeBuilder(segments);
}
public StructuredDataBuilder structuredData() {
return new StructuredDataBuilder(segments);
}
public MetadataPackBuilder metadataPack() {
return new MetadataPackBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class TenantBuilder extends Path.TenantBuilder {
private TenantBuilder(List list) {
super(list, RelativePath::new);
}
@Override
protected EnvironmentBuilder environmentBuilder(List list) {
return new EnvironmentBuilder(list);
}
@Override
protected FeedBuilder feedBuilder(List segments) {
return new FeedBuilder(segments);
}
@Override
protected ResourceTypeBuilder resourceTypeBuilder(List list) {
return new ResourceTypeBuilder(list);
}
@Override
protected MetricTypeBuilder metricTypeBuilder(List list) {
return new MetricTypeBuilder(list);
}
@Override
protected MetadataPackBuilder metadataPackBuilder(List segments) {
return new MetadataPackBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class EnvironmentBuilder extends Path.EnvironmentBuilder {
private EnvironmentBuilder(List list) {
super(list, RelativePath::new);
}
@Override
protected ResourceBuilder resourceBuilder(List segments) {
return new ResourceBuilder(segments);
}
@Override
protected MetricBuilder metricBuilder(List segments) {
return new MetricBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class ResourceTypeBuilder extends Path.ResourceTypeBuilder {
private ResourceTypeBuilder(List list) {
super(list, RelativePath::new);
}
@Override
protected OperationTypeBuilder operationTypeBuilder(List segments) {
return new OperationTypeBuilder(segments);
}
@Override protected StructuredDataBuilder structuredDataBuilder(List segments) {
return new StructuredDataBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class MetricTypeBuilder extends Path.MetricTypeBuilder {
private MetricTypeBuilder(List segments) {
super(segments, RelativePath::new);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class OperationTypeBuilder extends Path.OperationTypeBuilder {
private OperationTypeBuilder(List segments) {
super(segments, RelativePath::new);
}
@Override
protected StructuredDataBuilder structuredDataBuilder(List segments) {
return new StructuredDataBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class ResourceBuilder extends Path.ResourceBuilder {
private ResourceBuilder(List segments) {
super(segments, RelativePath::new);
}
@Override protected MetricBuilder metricBuilder(List segments) {
return new MetricBuilder(segments);
}
@Override
protected StructuredDataBuilder structuredDataBuilder(List segments) {
return new StructuredDataBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class MetricBuilder extends Path.MetricBuilder {
private MetricBuilder(List segments) {
super(segments, RelativePath::new);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class FeedBuilder extends Path.FeedBuilder {
private FeedBuilder(List list) {
super(list, RelativePath::new);
}
@Override
protected ResourceTypeBuilder resourceTypeBuilder(List segments) {
return new ResourceTypeBuilder(segments);
}
@Override
protected MetricTypeBuilder metricTypeBuilder(List segments) {
return new MetricTypeBuilder(segments);
}
@Override
protected ResourceBuilder resourceBuilder(List segments) {
return new ResourceBuilder(segments);
}
@Override
protected MetricBuilder metricBuilder(List segments) {
return new MetricBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class RelationshipBuilder extends Path.RelationshipBuilder {
private RelationshipBuilder(List segments) {
super(segments, RelativePath::new);
}
}
public static final class StructuredDataBuilder extends Path.StructuredDataBuilder {
private StructuredDataBuilder(List segments) {
super(segments, RelativePath::new);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return new UpBuilder(segments);
}
}
public static final class MetadataPackBuilder extends Path.MetadataPackBuilder {
private MetadataPackBuilder(List segments) {
super(segments, RelativePath::new);
}
}
public static class UpBuilder extends AbstractBuilder {
UpBuilder(List segments) {
super(segments, RelativePath::new);
}
public TenantBuilder tenant(String id) {
segments.add(new Segment(SegmentType.t, id));
return new TenantBuilder(segments);
}
public EnvironmentBuilder environment(String id) {
segments.add(new Segment(SegmentType.e, id));
return new EnvironmentBuilder(segments);
}
public ResourceTypeBuilder resourceType(String id) {
segments.add(new Segment(SegmentType.rt, id));
return new ResourceTypeBuilder(segments);
}
public MetricTypeBuilder metricType(String id) {
segments.add(new Segment(SegmentType.mt, id));
return new MetricTypeBuilder(segments);
}
public FeedBuilder feed(String id) {
segments.add(new Segment(SegmentType.f, id));
return new FeedBuilder(segments);
}
public ResourceBuilder resource(String id) {
segments.add(new Segment(SegmentType.r, id));
return new ResourceBuilder(segments);
}
public MetricBuilder metric(String id) {
segments.add(new Segment(SegmentType.m, id));
return new MetricBuilder(segments);
}
public StructuredDataBuilder dataEntity(String role) {
segments.add(new Segment(SegmentType.d, role));
return new StructuredDataBuilder(segments);
}
public OperationTypeBuilder operationType(String id) {
segments.add(new Segment(SegmentType.ot, id));
return new OperationTypeBuilder(segments);
}
public StructuredDataBuilder structuredData() {
return new StructuredDataBuilder(segments);
}
public UpBuilder up() {
segments.add(new Segment(SegmentType.up, null));
return this;
}
@Override
public RelativePath get() {
return super.get();
}
}
public static class Extender extends Path.Extender {
Extender(int from, List segments) {
this(from, segments, (segs) -> {
if (segs.isEmpty()) {
return SegmentType.getRelativeShortNames();
}
SegmentType lastType = segs.get(segs.size() - 1).getElementType();
int idx = segs.size() - 2;
int jump = 1;
while (SegmentType.up.equals(lastType)) {
while (idx >= 0 && SegmentType.up.equals(segs.get(idx).getElementType())) {
idx--;
jump++;
}
idx -= jump;
if (idx < 0) {
return SegmentType.getRelativeShortNames();
} else if (idx >= 0) {
lastType = segs.get(idx).getElementType();
}
}
return VALID_PROGRESSIONS.get(lastType);
});
}
Extender(int from, List segments, Function, Collection> validProgressions) {
super(from, segments, false, validProgressions);
}
@Override
protected RelativePath newPath(int startIdx, int endIdx, List segments) {
return new RelativePath(startIdx, endIdx, segments);
}
@Override
public Extender extend(Segment segment) {
return (Extender) super.extend(segment);
}
public Extender extend(Collection segments) {
return (Extender) super.extend(segments);
}
@Override
public Extender extend(SegmentType type, String id) {
return (Extender) super.extend(type, id);
}
public Extender extendUp() {
return (Extender) super.extend(new Segment(SegmentType.up, null));
}
@Override
public RelativePath get() {
return (RelativePath) super.get();
}
}
private static class RelativeTypeProvider extends EnhancedTypeProvider {
private final TypeProvider wrapped;
private RelativeTypeProvider(TypeProvider wrapped) {
this.wrapped = wrapped;
}
@Override
public void segmentParsed(Segment segment) {
if (wrapped != null) {
wrapped.segmentParsed(segment);
}
}
@Override
public Segment deduceSegment(String type, String id, boolean isLast) {
if (type != null && !type.isEmpty()) {
SegmentType cls = SegmentType.fastValueOf(type);
if (!SegmentType.up.equals(cls) && (id == null || id.isEmpty())) {
return null;
} else if (id == null || id.isEmpty()) {
return new Segment(cls, null); //cls == up
} else if (SegmentType.up.equals(cls)) {
throw new IllegalArgumentException("The \"up\" path segment cannot have an id.");
} else {
return new Segment(cls, id);
}
}
if (id == null || id.isEmpty()) {
return null;
}
SegmentType cls = SegmentType.fastValueOf(id);
if (cls == null && wrapped != null) {
return wrapped.deduceSegment(type, id, isLast);
} else if (SegmentType.up.equals(cls)) {
return new Segment(cls, null);
} else {
return null;
}
}
@Override
public void finished() {
if (wrapped != null) {
wrapped.finished();
}
}
@Override
Set getValidTypeName() {
return SHORT_NAME_TYPES.keySet();
}
}
}