001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.weaver.model;
020
021import java.lang.annotation.Annotation;
022import java.lang.annotation.RetentionPolicy;
023import java.lang.reflect.AnnotatedElement;
024import java.util.Arrays;
025import java.util.LinkedHashSet;
026import java.util.Set;
027
028import org.apache.commons.lang3.Validate;
029import org.apache.commons.lang3.builder.HashCodeBuilder;
030
031/**
032 * {@link Weavable} extends {@link AnnotatedElement} to include
033 * {@link RetentionPolicy#CLASS} annotations.
034 *
035 * @param <SELF> own type
036 * @param <TARGET> target type
037 */
038public abstract class Weavable<SELF extends Weavable<SELF, TARGET>, TARGET> implements Comparable<SELF>,
039    AnnotatedElement {
040    private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
041
042    private final TARGET target;
043    private Set<Annotation> annotations;
044
045    /**
046     * Create a new {@link Weavable} instance.
047     * @param target {@code TARGET}
048     */
049    protected Weavable(final TARGET target) {
050        this.target = target;
051        if (target instanceof AnnotatedElement) {
052            addAnnotations(((AnnotatedElement) target).getAnnotations());
053        }
054    }
055
056    /**
057     * Add one or more annotations.
058     * @param toAdd {@link Annotation}[]
059     * @return whether any change was made
060     */
061    public final boolean addAnnotations(final Annotation... toAdd) {
062        Validate.noNullElements(toAdd);
063        return addAnnotations(Arrays.asList(toAdd));
064    }
065
066    /**
067     * Add annotations from an {@link Iterable}.
068     * @param toAdd {@link Iterable} of {@link Annotation}
069     * @return whether any change was made
070     */
071    public final boolean addAnnotations(final Iterable<Annotation> toAdd) {
072        if (toAdd == null) {
073            return false;
074        }
075        synchronized (this) {
076            if (annotations == null) {
077                annotations = new LinkedHashSet<Annotation>();
078            }
079            boolean result = false;
080            for (final Annotation ann : toAdd) {
081                if (ann == null) {
082                    continue;
083                }
084                result = annotations.add(ann) || result;
085            }
086            return result;
087        }
088    }
089
090    /**
091     * Get the target of this {@link Weavable}.
092     * @return {@code TARGET}
093     */
094    public TARGET getTarget() {
095        return target;
096    }
097
098    /**
099     * Get all {@link Annotation}s associated with this element.
100     * @return {@link Annotation}[]
101     */
102    @Override
103    public final synchronized Annotation[] getAnnotations() {
104        if (annotations == null) {
105            return EMPTY_ANNOTATION_ARRAY; //NOPMD - no problem sharing zero-length array
106        }
107        return annotations.toArray(new Annotation[annotations.size()]);
108    }
109
110    /**
111     * Get any instance of {@code annotationClass} attached to {@link #getTarget()}.
112     * @param annotationClass {@link Class} annotation type
113     * @param <T> annotation type
114     * @return {@code T} instance if available, else {@code null}
115     */
116    @Override
117    public synchronized <T extends Annotation> T getAnnotation(final Class<T> annotationClass) {
118        if (annotations == null) {
119            return null;
120        }
121        for (final Annotation prospect : annotations) {
122            if (annotationClass.equals(prospect.annotationType())) {
123                @SuppressWarnings("unchecked")
124                final T result = (T) prospect;
125                return result;
126            }
127        }
128        return null;
129    }
130
131    /**
132     * Overridden to return {@link #getAnnotations()}.
133     * @return {@link Annotation}[]
134     */
135    @Override
136    public final Annotation[] getDeclaredAnnotations() {
137        return getAnnotations();
138    }
139
140    /**
141     * Learn whether an annotation of type {@code annotationClass} is present.
142     * @param annotationClass to find
143     * @return {@code boolean}
144     */
145    @Override
146    public boolean isAnnotationPresent(final Class<? extends Annotation> annotationClass) {
147        return getAnnotation(annotationClass) != null;
148    }
149
150    /**
151     * Return a {@link String} representation of this {@link Weavable}.
152     * @return {@link String}
153     */
154    @Override
155    public String toString() {
156        return "Weavable " + getTarget().toString();
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    @Override
163    public boolean equals(final Object obj) {
164        if (obj == this) {
165            return true;
166        }
167        if (!getClass().isInstance(obj)) {
168            return false;
169        }
170        return getTarget().equals(((Weavable<?, ?>) obj).getTarget());
171    }
172
173    /**
174     * {@inheritDoc}
175     */
176    @Override
177    public int hashCode() {
178        return new HashCodeBuilder().append(getTarget()).toHashCode();
179    }
180}