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