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 */
019
020package org.apache.isis.core.metamodel.consent;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025
026import org.apache.isis.applib.events.InteractionEvent;
027
028public class InteractionResult {
029
030    /**
031     * Initially {@link #ADVISING}; when call
032     * {@link InteractionResult#getInteractionEvent()}, flips over into
033     * {@link #ADVISED}.
034     * 
035     * <p>
036     * Subsequent attempts to
037     * {@link InteractionResult#advise(String, InteractionAdvisor)} will then be
038     * disallowed.
039     */
040    enum State {
041        ADVISING, ADVISED
042    }
043
044    private final InteractionEvent interactionEvent;
045    private final StringBuilder reasonBuf = new StringBuilder();
046    private final List<InteractionAdvisor> advisors = new ArrayList<InteractionAdvisor>();
047
048    private State state = State.ADVISING;
049
050    public InteractionResult(final InteractionEvent interactionEvent) {
051        this.interactionEvent = interactionEvent;
052    }
053
054    /**
055     * Returns the contained {@link InteractionEvent}, if necessary updated with
056     * the {@link #advise(String, InteractionAdvisor) advice} of the
057     * interactions.
058     * 
059     * <p>
060     * That is, if still {@link State#ADVISING advising}, then copies over the
061     * details from this result into the contained {@link InteractionEvent}, and
062     * flips into {@link State#ADVISED advised (done)}.
063     * 
064     * @return
065     */
066    public InteractionEvent getInteractionEvent() {
067        if (state == State.ADVISING) {
068            interactionEvent.advised(getReason(), getAdvisorClass());
069            state = State.ADVISED;
070        }
071        return interactionEvent;
072    }
073
074    private Class<?> getAdvisorClass() {
075        final InteractionAdvisor advisor = getAdvisor();
076        return advisor != null ? advisor.getClass() : null;
077    }
078
079    public void advise(final String reason, final InteractionAdvisor facet) {
080        if (state == State.ADVISED) {
081            throw new IllegalStateException("Cannot append since have called getInteractionEvent");
082        }
083        if (reason == null) {
084            return;
085        }
086        if (isVetoing()) {
087            reasonBuf.append("; ");
088        }
089        advisors.add(facet);
090        reasonBuf.append(reason);
091    }
092
093    public boolean isVetoing() {
094        return !isNotVetoing();
095    }
096
097    public boolean isNotVetoing() {
098        return reasonBuf.length() == 0;
099    }
100
101    /**
102     * Returns the first of the {@link #getAdvisors()} that has been
103     * {@link #advise(String, InteractionAdvisor) advised} , or <tt>null</tt> if
104     * none yet.
105     * 
106     * @see #getAdvisorFacets()
107     */
108    public InteractionAdvisor getAdvisor() {
109        return advisors.size() >= 1 ? advisors.get(0) : null;
110    }
111
112    /**
113     * Returns all {@link InteractionAdvisor advisor} (facet)s that have
114     * {@link #advise(String, InteractionAdvisor) append}ed reasons to the
115     * buffer.
116     * 
117     * @see #getAdvisor()
118     */
119    public List<InteractionAdvisor> getAdvisorFacets() {
120        return Collections.unmodifiableList(advisors);
121    }
122
123    public Consent createConsent() {
124        if (isNotVetoing()) {
125            return new Allow(this);
126        } else {
127            return new Veto(this);
128        }
129    }
130
131    /**
132     * Gets the reason as currently known, but does not change the state.
133     * 
134     * <p>
135     * If {@link #isNotVetoing()}, then returns <tt>null</tt>. Otherwise will be
136     * a non-empty string.
137     */
138    public String getReason() {
139        return isNotVetoing() ? null : reasonBuf.toString();
140    }
141
142    @Override
143    public String toString() {
144        return String.format("%s: %s: %s (%d facets advised)", interactionEvent, state, toStringInterpret(reasonBuf), advisors.size());
145    }
146
147    private String toStringInterpret(final StringBuilder reasonBuf) {
148        if (getReason().length() == 0) {
149            return "allowed";
150        } else {
151            return "vetoed";
152        }
153    }
154
155}