1/*
2 * Copyright (C) 2015 Google, Inc.
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 */
16package dagger.internal.codegen;
17
18import com.google.auto.common.AnnotationMirrors;
19import com.google.auto.common.MoreTypes;
20import com.google.common.base.Optional;
21import com.google.common.base.Preconditions;
22import javax.annotation.Nullable;
23import javax.inject.Singleton;
24import javax.lang.model.element.AnnotationMirror;
25import javax.lang.model.element.Element;
26import javax.lang.model.element.TypeElement;
27
28import static com.google.auto.common.MoreTypes.isTypeOf;
29import static dagger.internal.codegen.ErrorMessages.stripCommonTypePrefixes;
30import static dagger.internal.codegen.InjectionAnnotations.getScopeAnnotation;
31
32/**
33 * A representation of the scope (or lack of it) associated with a component, providing method
34 * or injection location.
35 */
36final class Scope {
37
38  /**
39   * An internal representation for an unscoped binding.
40   */
41  private static final Scope UNSCOPED = new Scope();
42
43  /**
44   * The underlying {@link AnnotationMirror} that represents the scope annotation.
45   */
46  @Nullable
47  private final AnnotationMirror annotationMirror;
48
49  private Scope(@Nullable AnnotationMirror annotationMirror) {
50    this.annotationMirror = annotationMirror;
51  }
52
53  private Scope() {
54    this(null);
55  }
56
57  /**
58   * Returns representation for an unscoped binding.
59   */
60  static Scope unscoped() {
61    return UNSCOPED;
62  }
63
64  /**
65   * If the source code element has an associated scoped annotation then returns a representation
66   * of that scope, otherwise returns a representation for an unscoped binding.
67   */
68  static Scope scopeOf(Element element) {
69    Optional<AnnotationMirror> scopeAnnotation = getScopeAnnotation(element);
70    return scopeAnnotation.isPresent() ? new Scope(scopeAnnotation.get()) : UNSCOPED;
71  }
72
73  /**
74   * Returns true if the scope is present, i.e. it's not unscoped binding.
75   */
76  public boolean isPresent() {
77    return annotationMirror != null;
78  }
79
80  /**
81   * Returns true if the scope represents the {@link Singleton @Singleton} annotation.
82   */
83  public boolean isSingleton() {
84    return annotationMirror != null
85        && isTypeOf(Singleton.class, annotationMirror.getAnnotationType());
86  }
87
88  /**
89   * Returns the readable source representation (name with @ prefix) of the annotation type.
90   *
91   * <p>It's readable source because it has had common package prefixes removed, e.g.
92   * {@code @javax.inject.Singleton} is returned as {@code @Singleton}.
93   *
94   * <p>Make sure that the scope is actually {@link #isPresent() present} before calling as it will
95   * throw an {@link IllegalStateException} otherwise. This does not return any annotation values
96   * as according to {@link javax.inject.Scope} scope annotations are not supposed to use them.
97   */
98  public String getReadableSource() {
99    return stripCommonTypePrefixes("@" + getQualifiedName());
100  }
101
102  /**
103   * Returns the fully qualified name of the annotation type.
104   *
105   * <p>Make sure that the scope is actually {@link #isPresent() present} before calling as it will
106   * throw an {@link IllegalStateException} otherwise. This does not return any annotation values
107   * as according to {@link javax.inject.Scope} scope annotations are not supposed to use them.
108   */
109  public String getQualifiedName() {
110    Preconditions.checkState(annotationMirror != null,
111        "Cannot create a stripped source representation of no annotation");
112    TypeElement typeElement = MoreTypes.asTypeElement(annotationMirror.getAnnotationType());
113    return typeElement.getQualifiedName().toString();
114  }
115
116  /**
117   * Scopes are equal if the underlying {@link AnnotationMirror} are equivalent according to
118   * {@link AnnotationMirrors#equivalence()}.
119   */
120  @Override
121  public boolean equals(Object obj) {
122    if (this == obj) {
123      return true;
124    } else if (obj instanceof Scope) {
125      Scope that = (Scope) obj;
126      return AnnotationMirrors.equivalence()
127        .equivalent(this.annotationMirror, that.annotationMirror);
128    } else {
129      return false;
130    }
131  }
132
133  @Override
134  public int hashCode() {
135    return AnnotationMirrors.equivalence().hash(annotationMirror);
136  }
137
138  /**
139   * Returns a debug representation of the scope.
140   */
141  @Override
142  public String toString() {
143    return annotationMirror == null ? "UNSCOPED" : annotationMirror.toString();
144  }
145}
146