001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.viewer.restfulobjects.rendering;
020
021import java.util.Collections;
022import java.util.List;
023
024import org.apache.isis.core.commons.internal.collections._Lists;
025import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
026import org.apache.isis.viewer.restfulobjects.applib.util.PathNode;
027import org.apache.isis.viewer.restfulobjects.rendering.util.FollowSpecUtil;
028
029public final class LinkFollowSpecs {
030
031    public static final LinkFollowSpecs create(final List<List<String>> links) {
032        final List<List<PathNode>> specs = FollowSpecUtil.asFollowSpecs(links);
033        return new LinkFollowSpecs(specs, Mode.FOLLOWING, null);
034    }
035
036    private enum Mode {
037        FOLLOWING, TERMINATED;
038    }
039
040    private final List<List<PathNode>> pathSpecs;
041    private final Mode mode;
042    // don't care about the key, just the criteria
043    private final List<PathNode> criteriaSpecs;
044
045    private LinkFollowSpecs(final List<List<PathNode>> pathSpecs, final Mode mode, final List<PathNode> criteriaSpecs) {
046        this.pathSpecs = pathSpecs;
047        this.mode = mode;
048        this.criteriaSpecs = criteriaSpecs;
049    }
050
051    /**
052     * A little algebra...
053     */
054    public LinkFollowSpecs follow(final String pathTemplate, final Object... args) {
055        final String path = String.format(pathTemplate, args);
056        if (path == null) {
057            return terminated();
058        }
059        if (mode == Mode.TERMINATED) {
060            return terminated();
061        }
062        final PathNode candidate = PathNode.parse(path);
063        if (mode == Mode.FOLLOWING) {
064            List<List<PathNode>> remainingPathSpecs = _Lists.newArrayList();
065            List<PathNode> firstSpecs = _Lists.newArrayList();
066            for(List<PathNode> spec: pathSpecs) {
067                if(spec.isEmpty()) {
068                    continue;
069                }
070                PathNode first = spec.get(0);
071                if(candidate.equals(first)) {
072                    List<PathNode> remaining = spec.subList(1, spec.size());
073                    firstSpecs.add(first);
074                    remainingPathSpecs.add(remaining);
075                }
076            }
077            if(!remainingPathSpecs.isEmpty()) {
078                return new LinkFollowSpecs(remainingPathSpecs, Mode.FOLLOWING, firstSpecs);
079            }
080            return terminated();
081        }
082        return terminated();
083    }
084
085    private static LinkFollowSpecs terminated() {
086        return new LinkFollowSpecs(Collections.<List<PathNode>>emptyList(), Mode.TERMINATED, Collections.<PathNode>emptyList());
087    }
088
089    /**
090     * Not public API; use {@link #matches(JsonRepresentation)}.
091     */
092    boolean isFollowing() {
093        return mode == Mode.FOLLOWING;
094    }
095
096    public boolean isTerminated() {
097        return mode == Mode.TERMINATED;
098    }
099
100    /**
101     * Ensure that every key present in the provided map matches the criterium.
102     *
103     * <p>
104     * Any keys in the criterium are ignored (these were matched on during the
105     * {@link #follow(String, Object...)} call).
106     */
107    public boolean matches(final JsonRepresentation jsonRepr) {
108        if (!isFollowing()) {
109            return false;
110        }
111        if(criteriaSpecs == null) {
112            return true;
113        }
114        for (PathNode criteriaSpec : criteriaSpecs) {
115            if(criteriaSpec.matches(jsonRepr)) {
116                return true;
117            }
118        }
119        return false;
120    }
121
122    @Override
123    public String toString() {
124        return mode + " : " + criteriaSpecs + " : " + pathSpecs;
125    }
126
127}