Relation.java revision 6a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.statementservice.retriever;
18
19import android.annotation.NonNull;
20
21import java.util.regex.Pattern;
22
23/**
24 * An immutable value type representing a statement relation with "kind" and "detail".
25 *
26 * <p> The set of kinds is enumerated by the API: <ul> <li> <b>delegate_permission</b>: The detail
27 * field specifies which permission to delegate. A statement involving this relation does not
28 * constitute a requirement to do the delegation, just a permission to do so. </ul>
29 *
30 * <p> We may add other kinds in the future.
31 *
32 * <p> The detail field is a lowercase alphanumeric string with underscores and periods allowed
33 * (matching the regex [a-z0-9_.]+), but otherwise unstructured. It is also possible to specify '*'
34 * (the wildcard character) as the detail if the relation applies to any detail in the specified
35 * kind.
36 */
37public final class Relation {
38
39    private static final Pattern KIND_PATTERN = Pattern.compile("^[a-z0-9_.]+$");
40    private static final Pattern DETAIL_PATTERN = Pattern.compile("^([a-z0-9_.]+|[*])$");
41
42    private static final String MATCH_ALL_DETAILS = "*";
43
44    private final String mKind;
45    private final String mDetail;
46
47    private Relation(String kind, String detail) {
48        mKind = kind;
49        mDetail = detail;
50    }
51
52    /**
53     * Returns the relation's kind.
54     */
55    @NonNull
56    public String getKind() {
57        return mKind;
58    }
59
60    /**
61     * Returns the relation's detail.
62     */
63    @NonNull
64    public String getDetail() {
65        return mDetail;
66    }
67
68    /**
69     * Creates a new Relation object for the specified {@code kind} and {@code detail}.
70     *
71     * @throws AssociationServiceException if {@code kind} or {@code detail} is not well formatted.
72     */
73    public static Relation create(@NonNull String kind, @NonNull String detail)
74            throws AssociationServiceException {
75        if (!KIND_PATTERN.matcher(kind).matches() || !DETAIL_PATTERN.matcher(detail).matches()) {
76            throw new AssociationServiceException("Relation not well formatted.");
77        }
78        return new Relation(kind, detail);
79    }
80
81    /**
82     * Creates a new Relation object from its string representation.
83     *
84     * @throws AssociationServiceException if the relation is not well formatted.
85     */
86    public static Relation create(@NonNull String relation) throws AssociationServiceException {
87        String[] r = relation.split("/", 2);
88        if (r.length != 2) {
89            throw new AssociationServiceException("Relation not well formatted.");
90        }
91        return create(r[0], r[1]);
92    }
93
94    /**
95     * Returns true if {@code relation} has the same kind and detail. If {@code
96     * relation.getDetail()} is wildcard (*) then returns true if the kind is the same.
97     */
98    public boolean matches(Relation relation) {
99        return getKind().equals(relation.getKind()) && (getDetail().equals(MATCH_ALL_DETAILS)
100                || getDetail().equals(relation.getDetail()));
101    }
102
103    /**
104     * Returns a string representation of this relation.
105     */
106    @Override
107    public String toString() {
108        StringBuilder relation = new StringBuilder();
109        relation.append(getKind());
110        relation.append("/");
111        relation.append(getDetail());
112        return relation.toString();
113    }
114
115    // equals() and hashCode() are generated by Android Studio.
116    @Override
117    public boolean equals(Object o) {
118        if (this == o) {
119            return true;
120        }
121        if (o == null || getClass() != o.getClass()) {
122            return false;
123        }
124
125        Relation relation = (Relation) o;
126
127        if (mDetail != null ? !mDetail.equals(relation.mDetail) : relation.mDetail != null) {
128            return false;
129        }
130        if (mKind != null ? !mKind.equals(relation.mKind) : relation.mKind != null) {
131            return false;
132        }
133
134        return true;
135    }
136
137    @Override
138    public int hashCode() {
139        int result = mKind != null ? mKind.hashCode() : 0;
140        result = 31 * result + (mDetail != null ? mDetail.hashCode() : 0);
141        return result;
142    }
143}
144