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.impl.backend.memory;
018
019import ca.stellardrift.permissionsex.context.ContextValue;
020import ca.stellardrift.permissionsex.impl.util.PCollections;
021import ca.stellardrift.permissionsex.subject.ImmutableSubjectData;
022import ca.stellardrift.permissionsex.subject.Segment;
023import ca.stellardrift.permissionsex.subject.SubjectRef;
024import org.checkerframework.checker.nullness.qual.Nullable;
025import org.pcollections.PMap;
026import org.pcollections.PSet;
027import org.pcollections.PVector;
028import org.spongepowered.configurate.objectmapping.ObjectMapper;
029import org.spongepowered.configurate.serialize.SerializationException;
030import org.spongepowered.configurate.objectmapping.ConfigSerializable;
031import org.spongepowered.configurate.objectmapping.meta.Setting;
032
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.function.BiFunction;
037import java.util.function.Function;
038import java.util.function.UnaryOperator;
039
040import static java.util.Objects.requireNonNull;
041
042public class MemorySubjectData implements ImmutableSubjectData {
043    protected static final ObjectMapper<MemorySegment> MAPPER;
044    static {
045        try {
046            MAPPER = ObjectMapper.factory().get(MemorySegment.class);
047        } catch (SerializationException e) {
048            throw new ExceptionInInitializerError(e); // This debug indicates a programming issue
049        }
050    }
051
052
053    protected final PMap<PSet<ContextValue<?>>, MemorySegment> segments;
054
055    protected MemorySubjectData() {
056        this.segments = PCollections.map();
057    }
058
059    protected MemorySubjectData(final Map<PSet<ContextValue<?>>, MemorySegment> segments) {
060        this.segments = PCollections.asMap(segments);
061    }
062
063    protected MemorySubjectData newData(final PMap<PSet<ContextValue<?>>, MemorySegment> contexts) {
064        if (contexts == this.segments) {
065            return this;
066        }
067        return new MemorySubjectData(contexts);
068    }
069
070    @Override
071    @SuppressWarnings({"unchecked", "rawtypes"})
072    public Map<Set<ContextValue<?>>, Segment> segments() {
073        return (Map) this.segments;
074    }
075
076    @Override
077    public ImmutableSubjectData withSegments(final BiFunction<Set<ContextValue<?>>, Segment, Segment> transformer) {
078        PMap<PSet<ContextValue<?>>, MemorySegment> output = this.segments;
079        for (final Map.Entry<PSet<ContextValue<?>>, MemorySegment> entry : this.segments.entrySet()) {
080            final MemorySegment mapped = MemorySegment.from(transformer.apply(entry.getKey(), entry.getValue()));
081            if (mapped != entry.getValue()) {
082                if (mapped.empty()) {
083                    output = output.minus(entry.getKey());
084                } else {
085                    output = output.plus(entry.getKey(), mapped);
086                }
087            }
088        }
089        return newData(output);
090    }
091
092    @Override
093    public ImmutableSubjectData withSegment(final Set<ContextValue<?>> contexts, final UnaryOperator<Segment> operation) {
094        final PSet<ContextValue<?>> pContexts = PCollections.asSet(contexts);
095        final Segment original = this.segments.get(pContexts);
096        final MemorySegment mapped = MemorySegment.from(operation.apply(original == null ? MemorySegment.create() : original));
097        if (original != mapped) {
098            return newData(mapped.empty() ? this.segments.minus(pContexts) : this.segments.plus(pContexts, mapped));
099        } else {
100            return this;
101        }
102    }
103
104    @Override
105    public <V> Map<Set<ContextValue<?>>, V> mapSegmentValues(final Function<Segment, V> mapper) {
106        requireNonNull(mapper, "mapper");
107        return PCollections.asMap(this.segments, (k, v) -> k, (k, v) -> mapper.apply(v));
108    }
109
110    @Override
111    public <V> @Nullable V mapSegment(final Set<ContextValue<?>> contexts, final Function<Segment, V> mapper) {
112        requireNonNull(mapper, "mapper");
113        final MemorySegment segment = this.segments.get(PCollections.asSet(requireNonNull(contexts, "contexts")));
114        if (segment != null) {
115            return mapper.apply(segment);
116        }
117        return null;
118    }
119
120    @Override
121    public MemorySegment segment(final Set<ContextValue<?>> contexts) {
122        MemorySegment res = this.segments.get(PCollections.asSet(contexts));
123        if (res == null) {
124            res = MemorySegment.create();
125        }
126        return res;
127    }
128
129    @Override
130    public ImmutableSubjectData withSegment(final Set<ContextValue<?>> contexts, final Segment segment) {
131        PMap<PSet<ContextValue<?>>, MemorySegment> segments;
132        if (segment.empty()) {
133            segments = this.segments.minus(PCollections.asSet(contexts));
134        } else {
135            segments = this.segments.plus(PCollections.asSet(contexts), MemorySegment.from(segment));
136        }
137        return segments == this.segments ? this : newData(segments);
138    }
139
140    @Override
141    public Set<PSet<ContextValue<?>>> activeContexts() {
142        return this.segments.keySet();
143    }
144
145    @Override
146    public String toString() {
147        return "MemorySubjectData{" +
148                "segments=" + this.segments +
149                '}';
150    }
151
152    @ConfigSerializable
153    protected static class MemorySegment implements Segment {
154        @Setting private PMap<String, Integer> permissions;
155        @Setting private PMap<String, String> options;
156        @Setting private PVector<SubjectRef<?>> parents;
157        @Nullable @Setting("permissions-default") private Integer defaultValue;
158
159        static MemorySegment create() {
160            return new MemorySegment(
161                    PCollections.map(),
162                    PCollections.map(),
163                    PCollections.vector(),
164                    null
165            );
166        }
167
168        static MemorySegment from(final Segment other) {
169            if (other instanceof MemorySegment) {
170                return (MemorySegment) other;
171            } else {
172                return new MemorySegment(
173                        PCollections.asMap(other.permissions()),
174                        PCollections.asMap(other.options()),
175                        PCollections.narrow(PCollections.asVector(other.parents())),
176                        other.fallbackPermission()
177                );
178            }
179        }
180
181        MemorySegment(final PMap<String, Integer> permissions, final PMap<String, String> options, final PVector<SubjectRef<?>> parents, final @Nullable Integer defaultValue) {
182            this.permissions = permissions;
183            this.options = options;
184            this.parents = parents;
185            this.defaultValue = defaultValue;
186        }
187
188
189        private MemorySegment() { // Objectmapper constructor
190        }
191
192        @Override
193        public Map<String, String> options() {
194            return this.options;
195        }
196
197        @Override
198        public MemorySegment withOption(final String key, final String value) {
199            return new MemorySegment(this.permissions, this.options.plus(key, value), this.parents, this.defaultValue);
200        }
201
202        @Override
203        public MemorySegment withoutOption(final String key) {
204            if (!this.options.containsKey(key)) {
205                return this;
206            }
207            return new MemorySegment(this.permissions, this.options.minus(key), this.parents, this.defaultValue);
208
209        }
210
211        @Override
212        public MemorySegment withOptions(final Map<String, String> values) {
213            return new MemorySegment(this.permissions, PCollections.asMap(values), this.parents, this.defaultValue);
214        }
215
216        @Override
217        public MemorySegment withoutOptions() {
218            return new MemorySegment(this.permissions, PCollections.map(), this.parents, this.defaultValue);
219        }
220
221        @Override
222        public Map<String, Integer> permissions() {
223            return this.permissions;
224        }
225
226        @Override
227        public MemorySegment withPermission(final String permission, final int value) {
228            return new MemorySegment(
229                    value == 0 ? this.permissions.minus(permission) : this.permissions.plus(permission, value),
230                    this.options,
231                    this.parents,
232                    this.defaultValue);
233
234        }
235
236        @Override
237        public MemorySegment withPermissions(final Map<String, Integer> values) {
238            return new MemorySegment(PCollections.asMap(values), this.options, this.parents, this.defaultValue);
239        }
240
241        @Override
242        public MemorySegment withoutPermissions() {
243            return new MemorySegment(PCollections.map(), this.options, this.parents, this.defaultValue);
244        }
245
246        @Override
247        public List<SubjectRef<?>> parents() {
248            return this.parents;
249        }
250
251        @Override
252        public <I> MemorySegment plusParent(final SubjectRef<I> parent) {
253            return new MemorySegment(this.permissions, this.options, this.parents.plus(0, SubjectRef.mapKeySafe(parent)), this.defaultValue);
254        }
255
256        @Override
257        public <I> MemorySegment minusParent(final SubjectRef<I> parent) {
258            if (this.parents.isEmpty()) {
259                return this;
260            }
261
262            return new MemorySegment(this.permissions, this.options, this.parents.minus(SubjectRef.mapKeySafe(parent)), this.defaultValue);
263        }
264
265        @Override
266        public MemorySegment withParents(List<SubjectRef<?>> parents) {
267            return new MemorySegment(this.permissions, this.options, PCollections.asVector(parents), this.defaultValue);
268        }
269
270        @Override
271        public MemorySegment withoutParents() {
272            return new MemorySegment(this.permissions, this.options, PCollections.vector(), this.defaultValue);
273        }
274
275        @Override
276        public int fallbackPermission() {
277            return this.defaultValue == null ? 0 : this.defaultValue;
278        }
279
280        @Override
281        public Segment withFallbackPermission(int defaultValue) {
282            return new MemorySegment(this.permissions, this.options, this.parents, defaultValue);
283        }
284
285        @Override
286        public Segment cleared() {
287            return new MemorySegment(PCollections.map(), PCollections.map(), PCollections.vector(), null);
288        }
289
290        @Override
291        public String toString() {
292            return "DataEntry{" +
293                    "permissions=" + this.permissions +
294                    ", options=" + this.options +
295                    ", parents=" + this.parents +
296                    ", defaultValue=" + this.defaultValue +
297                    '}';
298        }
299
300        @Override
301        public boolean empty() {
302            return this.permissions.isEmpty()
303                    && this.options.isEmpty()
304                    && this.parents.isEmpty()
305                    && this.defaultValue == null;
306        }
307    }
308}