001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.reflect;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.lang.annotation.*;
024import java.net.*;
025import java.util.*;
026import java.util.function.*;
027import java.util.stream.*;
028
029import org.apache.juneau.commons.collections.*;
030import org.apache.juneau.commons.utils.*;
031
032/**
033 * Lightweight wrapper around a {@link Package} object providing convenient access to package metadata and annotations.
034 *
035 * <p>
036 * This class provides a cached wrapper around Java {@link Package} objects, extending the standard API with
037 * convenient methods for accessing package annotations and metadata. Instances are cached and reused for efficiency.
038 *
039 * <h5 class='section'>Features:</h5>
040 * <ul class='spaced-list'>
041 *    <li>Cached instances - package info objects are cached and reused
042 *    <li>Annotation support - get annotations declared on the package
043 *    <li>Convenient access - easy access to package name and metadata
044 *    <li>Thread-safe - instances are immutable and safe for concurrent access
045 * </ul>
046 *
047 * <h5 class='section'>Use Cases:</h5>
048 * <ul class='spaced-list'>
049 *    <li>Accessing package-level annotations
050 *    <li>Working with package metadata
051 *    <li>Building frameworks that need to analyze package information
052 * </ul>
053 *
054 * <h5 class='section'>Usage:</h5>
055 * <p class='bjava'>
056 *    <jc>// Get PackageInfo from a class</jc>
057 *    PackageInfo <jv>pi</jv> = PackageInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
058 *
059 *    <jc>// Get package name</jc>
060 *    String <jv>name</jv> = <jv>pi</jv>.getName();
061 *
062 *    <jc>// Get annotations</jc>
063 *    List&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>annotations</jv> =
064 *       <jv>pi</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).toList();
065 * </p>
066 *
067 * <h5 class='section'>See Also:</h5><ul>
068 *    <li class='jc'>{@link ClassInfo} - Class introspection
069 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a>
070 * </ul>
071 */
072public class PackageInfo implements Annotatable {
073
074   //-----------------------------------------------------------------------------------------------------------------
075   // Static
076   //-----------------------------------------------------------------------------------------------------------------
077
078   private static final Cache<Package,PackageInfo> CACHE = Cache.of(Package.class, PackageInfo.class).build();
079
080   /**
081    * Returns a package info wrapper around the package of the specified class.
082    *
083    * @param childClass The class whose package to retrieve.
084    * @return A package info wrapper.
085    */
086   public static PackageInfo of(Class<?> childClass) {
087      return of(childClass.getPackage());
088   }
089
090   /**
091    * Returns a package info wrapper around the package of the specified class info.
092    *
093    * @param childClass The class info whose package to retrieve.
094    * @return A package info wrapper.
095    */
096   public static PackageInfo of(ClassInfo childClass) {
097      return childClass.getPackage();
098   }
099
100   /**
101    * Returns a package info wrapper around the specified package object.
102    *
103    * @param inner The package object.
104    * @return A package info wrapper.
105    */
106   public static PackageInfo of(Package inner) {
107      return CACHE.get(inner, () -> new PackageInfo(inner));
108   }
109
110   //-----------------------------------------------------------------------------------------------------------------
111   // Instance
112   //-----------------------------------------------------------------------------------------------------------------
113
114   private final Package inner;
115   private final Supplier<List<AnnotationInfo<Annotation>>> annotations;  // All annotations on this package, wrapped in AnnotationInfo. Repeated annotations have been unwrapped and are present as individual instances.
116
117   /**
118    * Constructor.
119    *
120    * @param inner The package object.
121    */
122   protected PackageInfo(Package inner) {
123      assertArgNotNull("inner", inner);
124      this.inner = inner;
125      this.annotations = mem(
126         () -> opt(inner).map(pkg -> stream(pkg.getAnnotations()).flatMap(a -> AnnotationUtils.streamRepeated(a)).map(a -> AnnotationInfo.of(this, a)).toList()).orElse(liste()));
127   }
128
129   /**
130    * Returns <jk>true</jk> if the specified object is equal to this package.
131    *
132    * <p>
133    * Two packages are equal if they have the same name.
134    *
135    * @param obj The object to compare to.
136    * @return <jk>true</jk> if the specified object is equal to this package.
137    */
138   @Override /* Object */
139   public boolean equals(Object obj) {
140      if (obj instanceof PackageInfo obj2)
141         return inner.equals(obj2.inner);
142      return inner.equals(obj);
143   }
144
145   //-----------------------------------------------------------------------------------------------------------------
146   // Package methods
147   //-----------------------------------------------------------------------------------------------------------------
148
149   @Override /* Annotatable */
150   public AnnotatableType getAnnotatableType() { return AnnotatableType.PACKAGE_TYPE; }
151
152   /**
153    * Returns all annotations on this package, wrapped in {@link AnnotationInfo} objects.
154    *
155    * <p>
156    * Cached and unmodifiable.
157    * Repeated annotations (from {@code @Repeatable} containers) have been automatically unwrapped and are present as individual instances in the list.
158    *
159    * @return All annotations present on this package, or an empty list if there are none.
160    */
161   public List<AnnotationInfo<Annotation>> getAnnotations() { return annotations.get(); }
162
163   /**
164    * Returns this package's annotations of the specified type (including repeated annotations), wrapped in {@link AnnotationInfo}.
165    *
166    * <p>
167    * Filters the cached annotations list by type.
168    *
169    * @param <A> The annotation type.
170    * @param type The annotation type.
171    * @return A stream of annotation infos of the specified type, never <jk>null</jk>.
172    */
173   @SuppressWarnings("unchecked")
174   public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> type) {
175      return getAnnotations().stream().filter(ai -> type.isInstance(ai.inner())).map(ai -> (AnnotationInfo<A>)ai);
176   }
177
178   /**
179    * Returns the title of this package's implementation.
180    *
181    * <p>
182    * Same as calling {@link Package#getImplementationTitle()}.
183    *
184    * @return The implementation title, or <jk>null</jk> if it is not known.
185    * @see Package#getImplementationTitle()
186    */
187   public String getImplementationTitle() { return inner.getImplementationTitle(); }
188
189   /**
190    * Returns the name of the organization that provided this package's implementation.
191    *
192    * <p>
193    * Same as calling {@link Package#getImplementationVendor()}.
194    *
195    * @return The implementation vendor, or <jk>null</jk> if it is not known.
196    * @see Package#getImplementationVendor()
197    */
198   public String getImplementationVendor() { return inner.getImplementationVendor(); }
199
200   /**
201    * Returns the version of this package's implementation.
202    *
203    * <p>
204    * Same as calling {@link Package#getImplementationVersion()}.
205    *
206    * @return The implementation version, or <jk>null</jk> if it is not known.
207    * @see Package#getImplementationVersion()
208    */
209   public String getImplementationVersion() { return inner.getImplementationVersion(); }
210
211   @Override /* Annotatable */
212   public String getLabel() { return getName(); }
213
214   /**
215    * Returns the name of this package.
216    *
217    * <p>
218    * Same as calling {@link Package#getName()}.
219    *
220    * <h5 class='section'>Example:</h5>
221    * <p class='bjava'>
222    *    PackageInfo <jv>pi</jv> = PackageInfo.<jsm>of</jsm>(String.<jk>class</jk>);
223    *    String <jv>name</jv> = <jv>pi</jv>.getName();  <jc>// "java.lang"</jc>
224    * </p>
225    *
226    * @return The fully-qualified name of this package.
227    * @see Package#getName()
228    */
229   public String getName() { return inner.getName(); }
230
231   /**
232    * Returns the title of the specification that this package implements.
233    *
234    * <p>
235    * Same as calling {@link Package#getSpecificationTitle()}.
236    *
237    * @return The specification title, or <jk>null</jk> if it is not known.
238    * @see Package#getSpecificationTitle()
239    */
240   public String getSpecificationTitle() { return inner.getSpecificationTitle(); }
241
242   /**
243    * Returns the name of the organization that maintains the specification implemented by this package.
244    *
245    * <p>
246    * Same as calling {@link Package#getSpecificationVendor()}.
247    *
248    * @return The specification vendor, or <jk>null</jk> if it is not known.
249    * @see Package#getSpecificationVendor()
250    */
251   public String getSpecificationVendor() { return inner.getSpecificationVendor(); }
252
253   /**
254    * Returns the version number of the specification that this package implements.
255    *
256    * <p>
257    * Same as calling {@link Package#getSpecificationVersion()}.
258    *
259    * <p>
260    * This version string must be a sequence of nonnegative decimal integers separated by periods
261    * and may have leading zeros.
262    *
263    * @return The specification version, or <jk>null</jk> if it is not known.
264    * @see Package#getSpecificationVersion()
265    */
266   public String getSpecificationVersion() { return inner.getSpecificationVersion(); }
267
268   /**
269    * Returns the hash code of this package.
270    *
271    * <p>
272    * Same as calling {@link Package#hashCode()}.
273    *
274    * @return The hash code of this package.
275    */
276   @Override /* Object */
277   public int hashCode() {
278      return inner.hashCode();
279   }
280
281   //-----------------------------------------------------------------------------------------------------------------
282   // Object methods
283   //-----------------------------------------------------------------------------------------------------------------
284
285   /**
286    * Returns the wrapped {@link Package} object.
287    *
288    * @return The wrapped {@link Package} object.
289    */
290   public Package inner() {
291      return inner;
292   }
293
294   /**
295    * Compares this package's specification version with a desired version.
296    *
297    * <p>
298    * Same as calling {@link Package#isCompatibleWith(String)}.
299    *
300    * <p>
301    * The version strings are compared by comparing each decimal integer in the version string.
302    *
303    * @param desired The desired version.
304    * @return <jk>true</jk> if this package's specification version is compatible with the desired version.
305    * @throws NumberFormatException If the desired or current version is not in the correct format.
306    * @see Package#isCompatibleWith(String)
307    */
308   public boolean isCompatibleWith(String desired) throws NumberFormatException {
309      return inner.isCompatibleWith(desired);  // HTT - Hard to test normal return path: requires package with version set in JAR manifest (Specification-Version header), which is difficult to set in test environment
310   }
311
312   /**
313    * Returns <jk>true</jk> if this package is sealed.
314    *
315    * <p>
316    * Same as calling {@link Package#isSealed()}.
317    *
318    * @return <jk>true</jk> if this package is sealed.
319    * @see Package#isSealed()
320    */
321   public boolean isSealed() { return inner.isSealed(); }
322
323   //-----------------------------------------------------------------------------------------------------------------
324   // Annotatable interface methods
325   //-----------------------------------------------------------------------------------------------------------------
326
327   /**
328    * Returns <jk>true</jk> if this package is sealed with respect to the specified code source URL.
329    *
330    * <p>
331    * Same as calling {@link Package#isSealed(URL)}.
332    *
333    * @param url The code source URL.
334    * @return <jk>true</jk> if this package is sealed with respect to the specified URL.
335    * @see Package#isSealed(URL)
336    */
337   public boolean isSealed(URL url) {
338      return inner.isSealed(url);
339   }
340
341   /**
342    * Returns the string representation of this package.
343    *
344    * <p>
345    * Same as calling {@link Package#toString()}.
346    *
347    * <h5 class='section'>Example:</h5>
348    * <p class='bjava'>
349    *    PackageInfo <jv>pi</jv> = PackageInfo.<jsm>of</jsm>(String.<jk>class</jk>);
350    *    String <jv>s</jv> = <jv>pi</jv>.toString();  <jc>// "package java.lang"</jc>
351    * </p>
352    *
353    * @return The string representation of this package.
354    * @see Package#toString()
355    */
356   @Override /* Object */
357   public String toString() {
358      return inner.toString();
359   }
360}