1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.attributes;
  17. import java.lang.reflect.Field;
  18. import java.lang.reflect.Constructor;
  19. import java.lang.reflect.Method;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.ArrayList;
  23. import java.util.HashMap;
  24. import java.util.HashSet;
  25. import java.util.Iterator;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.WeakHashMap;
  29. /**
  30. * API for accessing attributes.
  31. *
  32. * <h3>General Notes on Errors</h3>
  33. *
  34. * All Methods in this class may throw <code>RepositoryError</code> or subclasses thereof.
  35. * This Error is thrown if an attribute repository can not be loaded for some Exceptional
  36. * reason.
  37. *
  38. * <h4>Rationale for Errors instead of Exceptions</h4>
  39. *
  40. * The methods in this class throws <code>Error</code>s instead of <code>Exception</code>s.
  41. * This rationale behind this is that:
  42. *
  43. * <ul>
  44. * <li>The programmer should not have to wrap all accesses to
  45. * the Attributes API in a try/catch clause.
  46. * <li>An Exception being thrown here is caused by missing classes
  47. * or other "Error-like" conditions.
  48. * </ul>
  49. *
  50. * <h3>Performance Notes</h3>
  51. * The process of loading attributes for a class is a
  52. * (relatively) time-consuming process, as it involves some dynamic linking
  53. * in the form of inheritable attributes, a lot of reflection and so on. However,
  54. * once loaded the attributes are cached, so repeated access to them are fast.
  55. * But despite this the process of finding one attribute of a given type
  56. * or such operations does involve some iteration of HashSets that <b>runs in linear
  57. * time in the number of attributes associated with the program element</b>, and you should
  58. * avoid accessing attributes in your innermost loops if you can avoid it. For
  59. * example, instead of:
  60. *
  61. * <pre><code>
  62. * Class myClass = ...;
  63. * for (int i = 0; i < 10000; i++) {
  64. * if (Attributes.hasAttributeType (myClass, MyAttribute.class)) {
  65. * doThis(myClass);
  66. * } else {
  67. * doThat(myClass);
  68. * }
  69. * }
  70. * </code></pre>
  71. *
  72. * do:
  73. *
  74. * <pre><code>
  75. * Class myClass = ...;
  76. * boolean shouldDoThis = Attributes.hasAttributeType (myClass, MyAttribute.class);
  77. * for (int i = 0; i < 10000; i++) {
  78. * if (shouldDoThis) {
  79. * doThis(myClass);
  80. * } else {
  81. * doThat(myClass);
  82. * }
  83. * }
  84. * </code></pre>
  85. *
  86. * if the loop should run at maximum speed.
  87. */
  88. public class Attributes {
  89. /**
  90. * A cache of attribute repositories. The map used is a WeakHashMap keyed on the
  91. * Class owning the attribute repository. This works because Class objects use
  92. * the identity function to compare for equality - i.e. if the two classes
  93. * have the same name, and are loaded from the same two ClassLoaders, then
  94. * <code>class1 == class2</code>. Also, <code>(class1.equals(class2)) == (class1 ==
  95. * class2)</code>. This means that a once a Class reference has been garbage-collected,
  96. * it can't be re-created. Thus we can treat the cache map as a normal map - the only
  97. * entries that will ever disappear are those we can't look up anyway because we
  98. * can't ever create the key for them!
  99. *
  100. * <p>Also, this will keep the cache from growing too big in environments where
  101. * classes are loaded and unloaded all the time (i.e. application servers).
  102. */
  103. private final static Map classRepositories = new WeakHashMap ();
  104. /**
  105. * List used to keep track of the initialization list in getCachedRepository.
  106. * Since the method is synchronized static, we only need one list.
  107. */
  108. private static List initList = new ArrayList ();
  109. private synchronized static CachedRepository getCachedRepository (Class clazz) throws RepositoryError, CircularDependencyError {
  110. if (classRepositories.containsKey (clazz)) {
  111. CachedRepository cr = (CachedRepository) classRepositories.get (clazz);
  112. if (cr == null) {
  113. // Circular references.
  114. List dependencyList = new ArrayList ();
  115. dependencyList.addAll (initList);
  116. throw new CircularDependencyError (clazz.getName (), dependencyList);
  117. } else {
  118. return cr;
  119. }
  120. } else {
  121. // Indicate that we're loading it.
  122. CachedRepository cached = null;
  123. initList.add (clazz.getName ());
  124. try {
  125. classRepositories.put (clazz, null);
  126. Class attributeRepo = null;
  127. AttributeRepositoryClass repo = EmptyAttributeRepositoryClass.INSTANCE;
  128. try {
  129. attributeRepo = Class.forName (clazz.getName () + "$__attributeRepository", true, clazz.getClassLoader ());
  130. repo = (AttributeRepositoryClass) attributeRepo.newInstance ();
  131. } catch (ClassNotFoundException cnfe) {
  132. // OK, just means no repo available, so default to empty one.
  133. repo = EmptyAttributeRepositoryClass.INSTANCE;
  134. } catch (InstantiationException ie) {
  135. throw new RepositoryError (ie);
  136. } catch (IllegalAccessException iae) {
  137. throw new RepositoryError (iae);
  138. }
  139. cached = new DefaultCachedRepository (clazz, repo);
  140. classRepositories.put (clazz, cached);
  141. if (repo != null) {
  142. Util.validateRepository (clazz, repo);
  143. }
  144. } finally {
  145. initList.remove (initList.size () - 1);
  146. }
  147. return cached;
  148. }
  149. }
  150. /**
  151. * Selects from a collection of attributes one attribute with a given class.
  152. *
  153. * @return the attribute instance, or null of none could be found.
  154. * @throws MultipleAttributesError if the collection contains more than one
  155. * instance of the specified class.
  156. */
  157. private static Object getAttribute (Collection attrs, Class attributeClass) throws MultipleAttributesError {
  158. Object candidate = null;
  159. Iterator iter = attrs.iterator ();
  160. while (iter.hasNext ()) {
  161. Object attr = iter.next ();
  162. if (attr.getClass () == attributeClass) {
  163. if (candidate == null) {
  164. candidate = attr;
  165. } else {
  166. throw new MultipleAttributesError (attributeClass.getName ());
  167. }
  168. }
  169. }
  170. return candidate;
  171. }
  172. /**
  173. * Get one attributes of a given type from a class.
  174. *
  175. * @return the attribute instance, or null of none could be found.
  176. * @throws MultipleAttributesError if the collection contains more than one
  177. * instance of the specified class.
  178. */
  179. public static Object getAttribute (Class clazz, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  180. return getAttribute (getAttributes (clazz), attributeClass);
  181. }
  182. /**
  183. * Get one attributes of a given type from a field.
  184. *
  185. * @return the attribute instance, or null of none could be found.
  186. * @throws MultipleAttributesError if the collection contains more than one
  187. * instance of the specified class.
  188. */
  189. public static Object getAttribute (Field field, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  190. return getAttribute (getAttributes (field), attributeClass);
  191. }
  192. /**
  193. * Get one attributes of a given type from a constructor.
  194. *
  195. * @return the attribute instance, or null of none could be found.
  196. * @throws MultipleAttributesError if the collection contains more than one
  197. * instance of the specified class.
  198. */
  199. public static Object getAttribute (Constructor ctor, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  200. return getAttribute (getAttributes (ctor), attributeClass);
  201. }
  202. /**
  203. * Get one attributes of a given type from a method.
  204. *
  205. * @return the attribute instance, or null of none could be found.
  206. * @throws MultipleAttributesError if the collection contains more than one
  207. * instance of the specified class.
  208. */
  209. public static Object getAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  210. return getAttribute (getAttributes (method), attributeClass);
  211. }
  212. /**
  213. * Get one attributes of a given type from a parameter.
  214. *
  215. * @return the attribute instance, or null of none could be found.
  216. * @throws MultipleAttributesError if the collection contains more than one
  217. * instance of the specified class.
  218. */
  219. public static Object getParameterAttribute (Method method, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  220. return getAttribute (getParameterAttributes (method, parameter), attributeClass);
  221. }
  222. /**
  223. * Get one attributes of a given type from a constructor's parameter.
  224. *
  225. * @return the attribute instance, or null of none could be found.
  226. * @throws MultipleAttributesError if the collection contains more than one
  227. * instance of the specified class.
  228. */
  229. public static Object getParameterAttribute (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  230. return getAttribute (getParameterAttributes (constructor, parameter), attributeClass);
  231. }
  232. /**
  233. * Get one attributes of a given type from a method's return value.
  234. *
  235. * @return the attribute instance, or null of none could be found.
  236. * @throws MultipleAttributesError if the collection contains more than one
  237. * instance of the specified class.
  238. */
  239. public static Object getReturnAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError {
  240. return getAttribute (getReturnAttributes (method), attributeClass);
  241. }
  242. /**
  243. * Gets all attributes for a class.
  244. */
  245. public static Collection getAttributes (Class clazz) throws RepositoryError {
  246. if (clazz == null) {
  247. throw new NullPointerException ("clazz");
  248. }
  249. return getCachedRepository (clazz).getAttributes ();
  250. }
  251. /**
  252. * Gets all attributes for a method.
  253. */
  254. public static Collection getAttributes (Method method) throws RepositoryError {
  255. if (method == null) {
  256. throw new NullPointerException ("method");
  257. }
  258. return getCachedRepository (method.getDeclaringClass()).getAttributes (method);
  259. }
  260. /**
  261. * Gets all attributes for a parameter of a method.
  262. */
  263. public static Collection getParameterAttributes (Method method, int parameter) throws RepositoryError {
  264. return getCachedRepository (method.getDeclaringClass()).getParameterAttributes (method, parameter);
  265. }
  266. /**
  267. * Gets all attributes for a parameter of a constructor.
  268. */
  269. public static Collection getParameterAttributes (Constructor constructor, int parameter) throws RepositoryError {
  270. if (constructor == null) {
  271. throw new NullPointerException ("constructor");
  272. }
  273. return getCachedRepository (constructor.getDeclaringClass()).getParameterAttributes (constructor, parameter);
  274. }
  275. /**
  276. * Gets all attributes for the return value of a method.
  277. */
  278. public static Collection getReturnAttributes (Method method) throws RepositoryError {
  279. if (method == null) {
  280. throw new NullPointerException ("method");
  281. }
  282. return getCachedRepository (method.getDeclaringClass()).getReturnAttributes (method);
  283. }
  284. /**
  285. * Gets all attributes for a field.
  286. */
  287. public static Collection getAttributes (Field field) throws RepositoryError {
  288. if (field == null) {
  289. throw new NullPointerException ("field");
  290. }
  291. return getCachedRepository (field.getDeclaringClass()).getAttributes (field);
  292. }
  293. /**
  294. * Gets all attributes for a constructor.
  295. */
  296. public static Collection getAttributes (Constructor cons) throws RepositoryError {
  297. if (cons == null) {
  298. throw new NullPointerException ("cons");
  299. }
  300. return getCachedRepository (cons.getDeclaringClass()).getAttributes (cons);
  301. }
  302. /**
  303. * Selects from a collection of attributes only those with a given class.
  304. */
  305. private static Collection getAttributes (Collection attrs, Class attributeClass) {
  306. HashSet result = new HashSet ();
  307. Iterator iter = attrs.iterator ();
  308. while (iter.hasNext ()) {
  309. Object attr = iter.next ();
  310. if (attr.getClass () == attributeClass) {
  311. result.add (attr);
  312. }
  313. }
  314. return Collections.unmodifiableCollection (result);
  315. }
  316. /**
  317. * Get all attributes of a given type from a class. For all objects o in the returned
  318. * collection, <code>o.getClass() == attributeClass</code>.
  319. */
  320. public static Collection getAttributes (Class clazz, Class attributeClass) throws RepositoryError {
  321. return getAttributes (getAttributes (clazz), attributeClass);
  322. }
  323. /**
  324. * Get all attributes of a given type from a field. For all objects o in the returned
  325. * collection, <code>o.getClass() == attributeClass</code>.
  326. */
  327. public static Collection getAttributes (Field field, Class attributeClass) throws RepositoryError {
  328. return getAttributes (getAttributes (field), attributeClass);
  329. }
  330. /**
  331. * Get all attributes of a given type from a constructor. For all objects o in the returned
  332. * collection, <code>o.getClass() == attributeClass</code>.
  333. */
  334. public static Collection getAttributes (Constructor ctor, Class attributeClass) throws RepositoryError {
  335. return getAttributes (getAttributes (ctor), attributeClass);
  336. }
  337. /**
  338. * Get all attributes of a given type from a method. For all objects o in the returned
  339. * collection, <code>o.getClass() == attributeClass</code>.
  340. */
  341. public static Collection getAttributes (Method method, Class attributeClass) throws RepositoryError {
  342. return getAttributes (getAttributes (method), attributeClass);
  343. }
  344. /**
  345. * Get all attributes of a given type from a method's parameter. For all objects o in the returned
  346. * collection, <code>o.getClass() == attributeClass</code>.
  347. */
  348. public static Collection getParameterAttributes (Method method, int parameter, Class attributeClass) throws RepositoryError {
  349. return getAttributes (getParameterAttributes (method, parameter), attributeClass);
  350. }
  351. /**
  352. * Get all attributes of a given type from a method's parameter. For all objects o in the returned
  353. * collection, <code>o.getClass() == attributeClass</code>.
  354. */
  355. public static Collection getParameterAttributes (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError {
  356. return getAttributes (getParameterAttributes (constructor, parameter), attributeClass);
  357. }
  358. /**
  359. * Get all attributes of a given type from a method's return value. For all objects o in the returned
  360. * collection, <code>o.getClass() == attributeClass</code>.
  361. */
  362. public static Collection getReturnAttributes (Method method, Class attributeClass) throws RepositoryError {
  363. return getAttributes (getReturnAttributes (method), attributeClass);
  364. }
  365. /**
  366. * Convenience function to test whether a collection of attributes contain
  367. * an attribute of a given class.
  368. */
  369. private static boolean hasAttributeType (Collection attrs, Class attributeClass) {
  370. Iterator iter = attrs.iterator ();
  371. while (iter.hasNext ()) {
  372. Object attr = iter.next ();
  373. if (attr.getClass () == attributeClass) {
  374. return true;
  375. }
  376. }
  377. return false;
  378. }
  379. /**
  380. * Tests if a class has an attribute of a given type. That is, is there any attribute
  381. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  382. */
  383. public static boolean hasAttributeType (Class clazz, Class attributeClass) throws RepositoryError {
  384. return hasAttributeType (getAttributes (clazz), attributeClass);
  385. }
  386. /**
  387. * Tests if a field has an attribute of a given type. That is, is there any attribute
  388. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  389. */
  390. public static boolean hasAttributeType (Field field, Class attributeClass) throws RepositoryError {
  391. return hasAttributeType (getAttributes (field), attributeClass);
  392. }
  393. /**
  394. * Tests if a constructor has an attribute of a given type. That is, is there any attribute
  395. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  396. */
  397. public static boolean hasAttributeType (Constructor ctor, Class attributeClass) throws RepositoryError {
  398. return hasAttributeType (getAttributes (ctor), attributeClass);
  399. }
  400. /**
  401. * Tests if a method has an attribute of a given type. That is, is there any attribute
  402. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  403. */
  404. public static boolean hasAttributeType (Method method, Class attributeClass) throws RepositoryError {
  405. return hasAttributeType (getAttributes (method), attributeClass);
  406. }
  407. /**
  408. * Tests if a method's parameter has an attribute of a given type. That is, is there any attribute
  409. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  410. */
  411. public static boolean hasParameterAttributeType (Method method, int parameter, Class attributeClass) throws RepositoryError {
  412. return hasAttributeType (getParameterAttributes (method, parameter), attributeClass);
  413. }
  414. /**
  415. * Tests if a constructor's parameter has an attribute of a given type. That is, is there any attribute
  416. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  417. */
  418. public static boolean hasParameterAttributeType (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError {
  419. return hasAttributeType (getParameterAttributes (constructor, parameter), attributeClass);
  420. }
  421. /**
  422. * Tests if a method's return value has an attribute of a given type. That is, is there any attribute
  423. * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
  424. */
  425. public static boolean hasReturnAttributeType (Method method, Class attributeClass) throws RepositoryError {
  426. return hasAttributeType (getReturnAttributes (method), attributeClass);
  427. }
  428. /**
  429. * Convenience function to test whether a collection of attributes contain
  430. * an attribute.
  431. */
  432. private static boolean hasAttribute (Collection attrs, Object attribute) throws RepositoryError {
  433. return attrs.contains (attribute);
  434. }
  435. /**
  436. * Tests if a class has an attribute. That is, is there any attribute
  437. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  438. */
  439. public static boolean hasAttribute (Class clazz, Object attribute) throws RepositoryError {
  440. return hasAttribute (getAttributes (clazz), attribute);
  441. }
  442. /**
  443. * Tests if a field has an attribute. That is, is there any attribute
  444. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  445. */
  446. public static boolean hasAttribute (Field field, Object attribute) throws RepositoryError {
  447. return hasAttribute (getAttributes (field), attribute);
  448. }
  449. /**
  450. * Tests if a constructor has an attribute. That is, is there any attribute
  451. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  452. */
  453. public static boolean hasAttribute (Constructor ctor, Object attribute) throws RepositoryError {
  454. return hasAttribute (getAttributes (ctor), attribute);
  455. }
  456. /**
  457. * Tests if a method has an attribute. That is, is there any attribute
  458. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  459. */
  460. public static boolean hasAttribute (Method method, Object attribute) throws RepositoryError {
  461. return hasAttribute (getAttributes (method), attribute);
  462. }
  463. /**
  464. * Tests if a method's parameter has an attribute. That is, is there any attribute
  465. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  466. */
  467. public static boolean hasParameterAttribute (Method method, int parameter, Object attribute) throws RepositoryError {
  468. return hasAttribute (getParameterAttributes (method, parameter), attribute);
  469. }
  470. /**
  471. * Tests if a constructor's parameter has an attribute. That is, is there any attribute
  472. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  473. */
  474. public static boolean hasParameterAttribute (Constructor constructor, int parameter, Object attribute) throws RepositoryError {
  475. return hasAttribute (getParameterAttributes (constructor, parameter), attribute);
  476. }
  477. /**
  478. * Tests if a method's return value has an attribute. That is, is there any attribute
  479. * <code>attr</code> such that <code>attr.equals(attribute)</code>?
  480. */
  481. public static boolean hasReturnAttribute (Method method, Object attribute) throws RepositoryError {
  482. return hasAttribute (getReturnAttributes (method), attribute);
  483. }
  484. /**
  485. * Set attributes for a given class. The class must not have attributes set for it already
  486. * (i.e. you can't redefine the attributes of a class at runtime). This because it
  487. * really messes up the concept of inheritable attributes, and because the attribute set
  488. * associated with a class is immutable, like the set of methods and fields.
  489. *
  490. * @param repo The repository. The repository will be sealed before any attributes are
  491. * set. This to guarantee that the repository remains constant
  492. * during setting.
  493. * @throws IllegalStateException if the class whose attributes are defined already have
  494. * attributes defined for it (even if it has no attributes).
  495. */
  496. public static synchronized void setAttributes (RuntimeAttributeRepository repo) throws IllegalStateException {
  497. repo.seal ();
  498. Class clazz = repo.getDefinedClass ();
  499. if (classRepositories.get (clazz) != null) {
  500. throw new IllegalStateException (clazz.getName ());
  501. }
  502. Util.validateRepository (clazz, repo);
  503. DefaultCachedRepository cached = new DefaultCachedRepository (clazz, repo);
  504. classRepositories.put (clazz, cached);
  505. }
  506. }