001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ca.stellardrift.permissionsex.subject;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025/**
026 * Subject data set for a specific configuration of properties.
027 *
028 * <p>Segments are immutable, so any changes will produce a new object.</p>
029 *
030 * @since 2.0.0
031 */
032public interface Segment {
033
034    int PERMISSION_UNSET = 0;
035
036    /**
037     * Get options registered in a single context set.
038     *
039     * @return the options, as an immutable map
040     * @since 2.0.0
041     */
042    Map<String, String> options();
043
044    /**
045     * Return a new segment with updated option information.
046     *
047     * @param key the key of the option to set. Must be unique.
048     * @param value the value to set attached to the key
049     * @return an updated segment
050     * @since 2.0.0
051     */
052    Segment withOption(final String key, final String value);
053
054    /**
055     * Return a new segment with an option removed, if it is present
056     *
057     * @param key the key of the option to remove
058     * @return an updated segment, or the same segment if no such option was present
059     * @since 2.0.0
060     */
061    Segment withoutOption(final String key);
062
063    /**
064     * Return a new segment with an entirely new set of options
065     *
066     * @param values the options to set
067     * @return an updated segment
068     * @since 2.0.0
069     */
070    Segment withOptions(final Map<String, String> values);
071
072    /**
073     * Return a new segment with all options unset.
074     *
075     * @return a new segment with no options information.
076     * @since 2.0.0
077     */
078    default Segment withoutOptions() {
079        return withOptions(Collections.emptyMap());
080    }
081
082    /**
083     * Get permissions data for a single context in this subject.
084     *
085     * @return an immutable map from permission to value
086     * @since 2.0.0
087     */
088    Map<String, Integer> permissions();
089
090    /**
091     * Set a single permission to a specific value.
092     *
093     * <p>Values greater than zero evaluate to true, values less than zero evaluate to false,
094     * and equal to zero will unset the permission. Higher absolute values carry more weight.</p>
095     *
096     * @param permission the permission to set
097     * @param value the value. Zero to unset.
098     * @return an updated segment
099     * @since 2.0.0
100     */
101    Segment withPermission(final String permission, final int value);
102
103    /**
104     * Set all permissions.
105     *
106     * @param values a map from permissions to their values
107     * @return an updated segment object
108     * @since 2.0.0
109     */
110    Segment withPermissions(Map<String, Integer> values);
111
112    /**
113     * Return a new segment with all permissions unset.
114     *
115     * @return the updated segment
116     * @since 2.0.0
117     */
118    Segment withoutPermissions();
119
120    /**
121     * Get parents in this segment
122     *
123     * @return an immutable list of parents
124     * @since 2.0.0
125     */
126    List<? extends SubjectRef<?>> parents();
127
128    /**
129     * Update a segment with an added parent.
130     *
131     * <p>This parent will be added at the beginning of the list of parents, meaning it will have
132     * higher priority than any existing parents.</p>
133     *
134     * @param type the type of the parent subject being added
135     * @param identifier the identifier of the parent subject being added
136     * @return an updated segment
137     * @since 2.0.0
138     */
139    default <I> Segment plusParent(SubjectType<I> type, I identifier) {
140        return this.plusParent(SubjectRef.subject(type, identifier));
141    }
142
143    /**
144     * Update a new segment with an added parent.
145     *
146     * <p>This parent will be added at the end of the list of parents, meaning it will have lower
147     * priority than any existing parents.</p>
148     *
149     * @param subject a reference to the subject that should be added as parent.
150     * @param <I> identifier type
151     * @return an updated segment
152     * @since 2.0.0
153     */
154    <I> Segment plusParent(SubjectRef<I> subject);
155
156    /**
157     * Update a new segment to remove the passed parent.
158     *
159     * @param type the type of the parent subject being removed
160     * @param identifier the identifier of the parent subject being removed
161     * @return an updated segment
162     * @since 2.0.0
163     */
164    default <I> Segment minusParent(SubjectType<I> type, I identifier) {
165        return this.minusParent(SubjectRef.subject(type, identifier));
166    }
167
168    /**
169     * Remove a single parent subject in this segment.
170     *
171     * @param subject a reference to the subject that should be added as parent.
172     * @param <I> identifier type
173     * @return an updated segment
174     * @since 2.0.0
175     */
176    <I> Segment minusParent(final SubjectRef<I> subject);
177
178    /**
179     * Update a new segment with the provided {@code parents}.
180     *
181     * @param parents the parents that will be written
182     * @return an updated segment
183     * @since 2.0.0
184     */
185    Segment withParents(List<SubjectRef<?>> parents);
186
187    /**
188     * Remove all parents from this segment.
189     *
190     * @return an updated segment
191     * @since 2.0.0
192     */
193    Segment withoutParents();
194
195    /**
196     * Get the fallback permissions value for this segment.
197     *
198     * This is the value that will be returned for permissions that do not match anything
199     * more specific.
200     *
201     * @return the default value in the given context set, or 0 if none is set.
202     * @since 2.0.0
203     */
204    int fallbackPermission();
205
206    /**
207     * Update a new segment with a new fallback permission.
208     *
209     * @param defaultValue the default value to apply. A default value of 0 is equivalent to unset
210     * @return an updated segment
211     * @since 2.0.0
212     */
213    Segment withFallbackPermission(int defaultValue);
214
215    /**
216     * Return a new segment with no data set.
217     *
218     * @return the cleared segment
219     * @since 2.0.0
220     */
221    Segment cleared();
222
223    /**
224     * Return {@code true} if this segment has no set data.
225     *
226     * @return if this sement is empty
227     * @since 2.0.0
228     */
229    boolean empty();
230
231    /**
232     * Add all data from the segment {@code other} to this one.
233     *
234     * @param other the source segment
235     * @return an updated segment
236     * @since 2.0.0
237     */
238    default Segment mergeFrom(final Segment other) {
239        if (other.empty()) {
240            return this;
241        } else if (this.empty()) {
242            return other;
243        }
244
245        Segment output = this;
246        final Map<String, Integer> permissions = other.permissions();
247        final Map<String, String> options = other.options();
248        final List<? extends SubjectRef<?>> parents = other.parents();
249
250        if (!permissions.isEmpty()) {
251            final Map<String, Integer> newPermissions = new HashMap<>(output.permissions());
252            newPermissions.putAll(permissions);
253            output = output.withPermissions(newPermissions);
254        }
255
256        if (!options.isEmpty()) {
257            final Map<String, String> newOptions = new HashMap<>(output.options());
258            newOptions.putAll(options);
259            output = output.withOptions(newOptions);
260        }
261
262        if (!parents.isEmpty()) {
263            final List<SubjectRef<?>> newParents = new ArrayList<>(output.parents());
264            newParents.addAll(parents);
265            output = output.withParents(newParents);
266        }
267
268        return output;
269    }
270}