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<AnnotationInfo<MyAnnotation>> <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}