View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.di;
20  
21  import java.lang.reflect.ParameterizedType;
22  import java.lang.reflect.Type;
23  import java.util.Objects;
24  
25  import org.apache.maven.api.annotations.Nullable;
26  import org.apache.maven.di.impl.ReflectionUtils;
27  import org.apache.maven.di.impl.Types;
28  import org.apache.maven.di.impl.Utils;
29  
30  /**
31   * The key defines an identity of a binding. In any DI, a key is usually a type of the object along
32   * with some optional tag to distinguish between bindings which make objects of the same type.
33   * <p>
34   * In Maven Inject, a key is also a type token - special abstract class that can store type information
35   * with the shortest syntax possible in Java.
36   * <p>
37   * For example, to create a key of type Map&lt;String, List&lt;Integer&gt;&gt;, you can just use
38   * this syntax: <code>new Key&lt;Map&lt;String, List&lt;Integer&gt;&gt;&gt;(){}</code>.
39   * <p>
40   * If your types are not known at compile time, you can use {@link Types#parameterizedType} to make a
41   * parameterized type and give it to a {@link #ofType Key.ofType} constructor.
42   *
43   * @param <T> binding type
44   */
45  public abstract class Key<T> {
46      private final Type type;
47      private final @Nullable Object qualifier;
48  
49      private int hash;
50  
51      protected Key() {
52          this(null);
53      }
54  
55      protected Key(@Nullable Object qualifier) {
56          this.type = Types.simplifyType(getTypeParameter());
57          this.qualifier = qualifier;
58      }
59  
60      protected Key(Type type, @Nullable Object qualifier) {
61          this.type = Types.simplifyType(type);
62          this.qualifier = qualifier;
63      }
64  
65      static final class KeyImpl<T> extends Key<T> {
66          KeyImpl(Type type, Object qualifier) {
67              super(type, qualifier);
68          }
69      }
70  
71      public static <T> Key<T> of(Class<T> type) {
72          return new KeyImpl<>(type, null);
73      }
74  
75      public static <T> Key<T> of(Class<T> type, @Nullable Object qualifier) {
76          return new KeyImpl<>(type, qualifier);
77      }
78  
79      public static <T> Key<T> ofType(Type type) {
80          return new KeyImpl<>(type, null);
81      }
82  
83      public static <T> Key<T> ofType(Type type, @Nullable Object qualifier) {
84          return new KeyImpl<>(type, qualifier);
85      }
86  
87      private Type getTypeParameter() {
88          // this cannot possibly fail so not even a check here
89          Type typeArgument = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
90          Object outerInstance = ReflectionUtils.getOuterClassInstance(this);
91          //		// the outer instance is null in static context
92          return outerInstance != null
93                  ? Types.bind(typeArgument, Types.getAllTypeBindings(outerInstance.getClass()))
94                  : typeArgument;
95      }
96  
97      public Type getType() {
98          return type;
99      }
100 
101     /**
102      * A shortcut for <code>{@link Types#getRawType(Type)}(key.getType())</code>.
103      * Also casts the result to a properly parameterized class.
104      */
105     @SuppressWarnings("unchecked")
106     public Class<T> getRawType() {
107         return (Class<T>) Types.getRawType(type);
108     }
109 
110     /**
111      * Returns a type parameter of the underlying type wrapped as a key with no qualifier.
112      *
113      * @throws IllegalStateException when underlying type is not a parameterized one.
114      */
115     public <U> Key<U> getTypeParameter(int index) {
116         if (type instanceof ParameterizedType) {
117             return new KeyImpl<>(((ParameterizedType) type).getActualTypeArguments()[index], null);
118         }
119         throw new IllegalStateException("Expected type from key " + getDisplayString() + " to be parameterized");
120     }
121 
122     public @Nullable Object getQualifier() {
123         return qualifier;
124     }
125 
126     /**
127      * Returns an underlying type with display string formatting (package names stripped)
128      * and prepended qualifier display string if this key has a qualifier.
129      */
130     public String getDisplayString() {
131         return (qualifier != null ? getQualifierDisplayString() + " " : "") + ReflectionUtils.getDisplayName(type);
132     }
133 
134     private String getQualifierDisplayString() {
135         if (qualifier instanceof String s) {
136             return s.isEmpty() ? "@Named" : "@Named(\"" + s + "\")";
137         }
138         String s = Utils.getDisplayString(qualifier);
139         return s;
140     }
141 
142     @Override
143     public boolean equals(Object o) {
144         if (this == o) {
145             return true;
146         }
147         if (!(o instanceof Key<?>)) {
148             return false;
149         }
150         Key<?> that = (Key<?>) o;
151         return type.equals(that.type) && Objects.equals(qualifier, that.qualifier);
152     }
153 
154     @Override
155     public int hashCode() {
156         int hashCode = hash;
157         if (hashCode == 0) {
158             hash = 31 * type.hashCode() + (qualifier == null ? 0 : qualifier.hashCode());
159         }
160         return hash;
161     }
162 
163     @Override
164     public String toString() {
165         return getDisplayString();
166     }
167 }