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.
34 */
35public final class Relation {
36
37    private static final Pattern KIND_PATTERN = Pattern.compile("^[a-z0-9_.]+$");
38    private static final Pattern DETAIL_PATTERN = Pattern.compile("^([a-z0-9_.]+)$");
39
40    private final String mKind;
41    private final String mDetail;
42
43    private Relation(String kind, String detail) {
44        mKind = kind;
45        mDetail = detail;
46    }
47
48    /**
49     * Returns the relation's kind.
50     */
51    @NonNull
52    public String getKind() {
53        return mKind;
54    }
55
56    /**
57     * Returns the relation's detail.
58     */
59    @NonNull
60    public String getDetail() {
61        return mDetail;
62    }
63
64    /**
65     * Creates a new Relation object for the specified {@code kind} and {@code detail}.
66     *
67     * @throws AssociationServiceException if {@code kind} or {@code detail} is not well formatted.
68     */
69    public static Relation create(@NonNull String kind, @NonNull String detail)
70            throws AssociationServiceException {
71        if (!KIND_PATTERN.matcher(kind).matches() || !DETAIL_PATTERN.matcher(detail).matches()) {
72            throw new AssociationServiceException("Relation not well formatted.");
73        }
74        return new Relation(kind, detail);
75    }
76
77    /**
78     * Creates a new Relation object from its string representation.
79     *
80     * @throws AssociationServiceException if the relation is not well formatted.
81     */
82    public static Relation create(@NonNull String relation) throws AssociationServiceException {
83        String[] r = relation.split("/", 2);
84        if (r.length != 2) {
85            throw new AssociationServiceException("Relation not well formatted.");
86        }
87        return create(r[0], r[1]);
88    }
89
90    /**
91     * Returns true if {@code relation} has the same kind and detail.
92     */
93    public boolean matches(Relation relation) {
94        return getKind().equals(relation.getKind()) && getDetail().equals(relation.getDetail());
95    }
96
97    /**
98     * Returns a string representation of this relation.
99     */
100    @Override
101    public String toString() {
102        StringBuilder relation = new StringBuilder();
103        relation.append(getKind());
104        relation.append("/");
105        relation.append(getDetail());
106        return relation.toString();
107    }
108
109    // equals() and hashCode() are generated by Android Studio.
110    @Override
111    public boolean equals(Object o) {
112        if (this == o) {
113            return true;
114        }
115        if (o == null || getClass() != o.getClass()) {
116            return false;
117        }
118
119        Relation relation = (Relation) o;
120
121        if (mDetail != null ? !mDetail.equals(relation.mDetail) : relation.mDetail != null) {
122            return false;
123        }
124        if (mKind != null ? !mKind.equals(relation.mKind) : relation.mKind != null) {
125            return false;
126        }
127
128        return true;
129    }
130
131    @Override
132    public int hashCode() {
133        int result = mKind != null ? mKind.hashCode() : 0;
134        result = 31 * result + (mDetail != null ? mDetail.hashCode() : 0);
135        return result;
136    }
137}
138