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.commons.io.serialization;
021
022import java.io.ObjectStreamClass;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.function.Predicate;
026import java.util.regex.Pattern;
027import java.util.stream.Stream;
028
029/**
030 * A predicate (boolean-valued function) of one argument to accept and reject classes.
031 * <p>
032 * The reject list takes precedence over the accept list.
033 * </p>
034 *
035 * @since 2.18.0
036 */
037public class ObjectStreamClassPredicate implements Predicate<ObjectStreamClass> {
038
039    // This is not a Set for now to avoid ClassNameMatchers requiring proper implementations of hashCode() and equals().
040    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
041
042    // This is not a Set for now to avoid ClassNameMatchers requiring proper implementations of hashCode() and equals().
043    private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();
044
045    /**
046     * Constructs a new instance.
047     */
048    public ObjectStreamClassPredicate() {
049        // empty
050    }
051
052    /**
053     * Accepts the specified classes for deserialization, unless they are otherwise rejected.
054     * <p>
055     * The reject list takes precedence over the accept list.
056     * </p>
057     *
058     * @param classes Classes to accept
059     * @return this object
060     */
061    public ObjectStreamClassPredicate accept(final Class<?>... classes) {
062        Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add);
063        return this;
064    }
065
066    /**
067     * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected.
068     * <p>
069     * The reject list takes precedence over the accept list.
070     * </p>
071     *
072     * @param matcher a class name matcher to <em>accept</em> objects.
073     * @return this instance.
074     */
075    public ObjectStreamClassPredicate accept(final ClassNameMatcher matcher) {
076        acceptMatchers.add(matcher);
077        return this;
078    }
079
080    /**
081     * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected.
082     * <p>
083     * The reject list takes precedence over the accept list.
084     * </p>
085     *
086     * @param pattern a Pattern for compiled regular expression.
087     * @return this instance.
088     */
089    public ObjectStreamClassPredicate accept(final Pattern pattern) {
090        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
091        return this;
092    }
093
094    /**
095     * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected.
096     * <p>
097     * The reject list takes precedence over the accept list.
098     * </p>
099     *
100     * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
101     *                 FilenameUtils.wildcardMatch}
102     * @return this instance.
103     */
104    public ObjectStreamClassPredicate accept(final String... patterns) {
105        Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add);
106        return this;
107    }
108
109    /**
110     * Rejects the specified classes for deserialization, even if they are otherwise accepted.
111     * <p>
112     * The reject list takes precedence over the accept list.
113     * </p>
114     *
115     * @param classes Classes to reject
116     * @return this instance.
117     */
118    public ObjectStreamClassPredicate reject(final Class<?>... classes) {
119        Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add);
120        return this;
121    }
122
123    /**
124     * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted.
125     * <p>
126     * The reject list takes precedence over the accept list.
127     * </p>
128     *
129     * @param m the matcher to use
130     * @return this instance.
131     */
132    public ObjectStreamClassPredicate reject(final ClassNameMatcher m) {
133        rejectMatchers.add(m);
134        return this;
135    }
136
137    /**
138     * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted.
139     * <p>
140     * The reject list takes precedence over the accept list.
141     * </p>
142     *
143     * @param pattern standard Java regexp
144     * @return this instance.
145     */
146    public ObjectStreamClassPredicate reject(final Pattern pattern) {
147        rejectMatchers.add(new RegexpClassNameMatcher(pattern));
148        return this;
149    }
150
151    /**
152     * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted.
153     * <p>
154     * The reject list takes precedence over the accept list.
155     * </p>
156     *
157     * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
158     *                 FilenameUtils.wildcardMatch}
159     * @return this instance.
160     */
161    public ObjectStreamClassPredicate reject(final String... patterns) {
162        Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add);
163        return this;
164    }
165
166    /**
167     * Tests that the ObjectStreamClass conforms to requirements.
168     * <p>
169     * The reject list takes precedence over the accept list.
170     * </p>
171     *
172     * @param objectStreamClass The ObjectStreamClass to test.
173     * @return true if the input is accepted, false if rejected, false if neither.
174     */
175    @Override
176    public boolean test(final ObjectStreamClass objectStreamClass) {
177        return test(objectStreamClass.getName());
178    }
179
180    /**
181     * Tests that the class name conforms to requirements.
182     * <p>
183     * The reject list takes precedence over the accept list.
184     * </p>
185     *
186     * @param name The class name to test.
187     * @return true if the input is accepted, false if rejected, false if neither.
188     */
189    public boolean test(final String name) {
190        // The reject list takes precedence over the accept list.
191        for (final ClassNameMatcher m : rejectMatchers) {
192            if (m.matches(name)) {
193                return false;
194            }
195        }
196        for (final ClassNameMatcher m : acceptMatchers) {
197            if (m.matches(name)) {
198                return true;
199            }
200        }
201        return false;
202    }
203
204}