com.squareup.wire.schema.IdentifierSet Maven / Gradle / Ivy
Show all versions of wire-schema Show documentation
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.wire.schema;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* A heterogeneous set of rules to include and exclude types and members. If a member is included in
* the set, its type is implicitly also included. A type that is included without a specific member
* implicitly includes all of that type's members, but not its nested types.
*
* Rules in this set may be in the following forms:
*
* - Package names, followed by {@code .*}, like {@code squareup.protos.person.*}. This matches
* types and services defined in the package and its descendant packages.
*
- Fully qualified type and service names, like {@code squareup.protos.person.Person}.
*
- Fully qualified member names, which are type names followed by a '#', followed by a member
* name, like {@code squareup.protos.person.Person#address}. Members may be fields, enum
* constants or RPCs.
*
*
* An identifier set populated with {@code Movie} and {@code Actor#name} contains all members of
* {@code Movie} (such as {@code Movie#name} and {@code Movie#release_date}). It contains the type
* {@code Actor} and one member {@code Actor#name}, but not {@code Actor#birth_date} or {@code
* Actor#oscar_count}.
*
*
This set has included identifiers and excluded identifiers, with excludes taking
* precedence over includes. That is, if a type {@code Movie} is in both the includes and the
* excludes, it is not contained in the set.
*
*
If the includes set is empty, that implies that all elements should be included. Use this to
* exclude unwanted types and members without also including everything else.
*
*
Despite the builder, instances of this class are not safe for concurrent use.
*/
public final class IdentifierSet {
private final ImmutableSet includes;
private final ImmutableSet excludes;
private final Set usedIncludes = new LinkedHashSet<>();
private final Set usedExcludes = new LinkedHashSet<>();
private IdentifierSet(Builder builder) {
this.includes = builder.includes.build();
this.excludes = builder.excludes.build();
}
public boolean isEmpty() {
return includes.isEmpty() && excludes.isEmpty();
}
/** Returns true if {@code type} is a root. */
public boolean includes(ProtoType type) {
return includes(type.toString());
}
/** Returns true if {@code protoMember} is a root. */
public boolean includes(ProtoMember protoMember) {
return includes(protoMember.toString());
}
/**
* Returns true if {@code identifier} or any of its enclosing identifiers is included. If any
* enclosing identifier is excluded, that takes precedence and this returns false.
*/
private boolean includes(String identifier) {
if (includes.isEmpty()) return !exclude(identifier);
String includeMatch = null;
String excludeMatch = null;
for (String rule = identifier; rule != null; rule = enclosing(rule)) {
if (excludes.contains(rule)) {
excludeMatch = rule;
}
if (includes.contains(rule)) {
includeMatch = rule;
}
}
if (excludeMatch != null) {
usedExcludes.add(excludeMatch);
return false;
}
if (includeMatch != null) {
usedIncludes.add(includeMatch);
return true;
}
return false;
}
/**
* Returns true if {@code type} should be excluded, even if it is a transitive dependency of a
* root. In that case, the referring member is also excluded.
*/
public boolean excludes(ProtoType type) {
return exclude(type.toString());
}
/** Returns true if {@code protoMember} should be excluded. */
public boolean excludes(ProtoMember protoMember) {
return exclude(protoMember.toString());
}
/** Returns true if {@code identifier} or any of its enclosing identifiers is excluded. */
private boolean exclude(String identifier) {
String excludeMatch = null;
for (String rule = identifier; rule != null; rule = enclosing(rule)) {
if (excludes.contains(rule)) {
excludeMatch = rule;
}
}
if (excludeMatch != null) {
usedExcludes.add(excludeMatch);
return true;
}
return false;
}
/**
* Returns the identifier or wildcard that encloses {@code identifier}, or null if it is not
* enclosed.
*
*
* - If {@code identifier} is a member this returns the enclosing type.
*
- If it is a type it returns the enclosing package with a wildcard, like {@code
* squareup.dinosaurs.*}.
*
- If it is a package with a wildcard, it returns the parent package with a wildcard, like
* {@code squareup.*}. The root wildcard is a lone asterisk, {@code *}.
*
*/
static String enclosing(String identifier) {
int hash = identifier.lastIndexOf('#');
if (hash != -1) return identifier.substring(0, hash);
int from = identifier.endsWith(".*") ? identifier.length() - 3 : identifier.length() - 1;
int dot = identifier.lastIndexOf('.', from);
if (dot != -1) return identifier.substring(0, dot) + ".*";
return !identifier.equals("*") ? "*" : null;
}
public Set unusedIncludes() {
return Sets.difference(includes, usedIncludes);
}
public Set unusedExcludes() {
return Sets.difference(excludes, usedExcludes);
}
public static final class Builder {
final ImmutableSet.Builder includes = ImmutableSet.builder();
final ImmutableSet.Builder excludes = ImmutableSet.builder();
public Builder include(String identifier) {
if (identifier == null) throw new NullPointerException("identifier == null");
includes.add(identifier);
return this;
}
public Builder include(Iterable identifiers) {
if (identifiers == null) throw new NullPointerException("identifiers == null");
includes.addAll(identifiers);
return this;
}
public Builder exclude(String identifier) {
if (identifier == null) throw new NullPointerException("identifier == null");
excludes.add(identifier);
return this;
}
public Builder exclude(Iterable identifiers) {
if (identifiers == null) throw new NullPointerException("identifiers == null");
excludes.addAll(identifiers);
return this;
}
public IdentifierSet build() {
return new IdentifierSet(this);
}
}
}