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