/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support.reflect.declaration;
import org.apache.log4j.Logger;
import spoon.reflect.code.CtComment;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.ModelConsistencyChecker;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.chain.CtFunction;
import spoon.reflect.visitor.chain.CtConsumableFunction;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.filter.AnnotationFilter;
import spoon.support.util.EmptyClearableList;
import spoon.support.util.EmptyClearableSet;
import spoon.support.visitor.HashcodeVisitor;
import spoon.support.visitor.TypeReferenceScanner;
import spoon.support.visitor.equals.CloneHelper;
import spoon.support.visitor.equals.EqualsVisitor;
import spoon.support.visitor.replace.ReplacementVisitor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static spoon.reflect.ModelElementContainerDefaultCapacities.ANNOTATIONS_CONTAINER_DEFAULT_CAPACITY;
import static spoon.reflect.ModelElementContainerDefaultCapacities.COMMENT_CONTAINER_DEFAULT_CAPACITY;
/**
* Contains the default implementation of most CtElement methods.
*
*/
public abstract class CtElementImpl implements CtElement, Serializable {
private static final long serialVersionUID = 1L;
protected static final Logger LOGGER = Logger.getLogger(CtElementImpl.class);
public static final String ERROR_MESSAGE_TO_STRING = "Error in printing the node. One parent isn't initialized!";
public static <T> List<T> emptyList() {
return EmptyClearableList.instance();
}
public static <T> Set<T> emptySet() {
return EmptyClearableSet.instance();
}
public static <T> List<T> unmodifiableList(List<T> list) {
return list.isEmpty() ? Collections.<T>emptyList() : Collections.unmodifiableList(list);
}
transient Factory factory;
protected CtElement parent;
List<CtAnnotation<? extends Annotation>> annotations = emptyList();
private List<CtComment> comments = emptyList();
SourcePosition position = SourcePosition.NOPOSITION;
Map<String, Object> metadata;
public CtElementImpl() {
super();
}
@Override
public String getShortRepresentation() {
return super.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
boolean ret = EqualsVisitor.equals(this, (CtElement) o);
// neat online testing of core Java contract
if (ret && !factory.getEnvironment().checksAreSkipped() && this.hashCode() != o.hashCode()) {
throw new IllegalStateException("violation of equal/hashcode contract between \n" + this.toString() + "\nand\n" + o.toString() + "\n");
}
return ret;
}
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
for (CtAnnotation<? extends Annotation> a : getAnnotations()) {
if (a.getAnnotationType().toString().equals(annotationType.getName().replace('$', '.'))) {
return ((CtAnnotation<A>) a).getActualAnnotation();
}
}
return null;
}
@SuppressWarnings("unchecked")
public <A extends Annotation> CtAnnotation<A> getAnnotation(CtTypeReference<A> annotationType) {
for (CtAnnotation<? extends Annotation> a : getAnnotations()) {
if (a.getAnnotationType().equals(annotationType)) {
return (CtAnnotation<A>) a;
}
}
return null;
}
public List<CtAnnotation<? extends Annotation>> getAnnotations() {
return unmodifiableList(annotations);
}
public String getDocComment() {
for (CtComment ctComment : comments) {
if (ctComment.getCommentType() == CtComment.CommentType.JAVADOC) {
return ctComment.getContent();
}
}
return null;
}
public SourcePosition getPosition() {
if (position != null) {
return position;
}
return null;
}
@Override
public int hashCode() {
HashcodeVisitor pr = new HashcodeVisitor();
pr.scan(this);
return pr.getHasCode();
}
public <E extends CtElement> E setAnnotations(List<CtAnnotation<? extends Annotation>> annotations) {
if (annotations == null || annotations.isEmpty()) {
this.annotations = CtElementImpl.emptyList();
return (E) this;
}
this.annotations.clear();
for (CtAnnotation<? extends Annotation> annot : annotations) {
addAnnotation(annot);
}
return (E) this;
}
@Override
public void delete() {
replace(null);
}
public <E extends CtElement> E addAnnotation(CtAnnotation<? extends Annotation> annotation) {
if (annotation == null) {
return (E) this;
}
if ((List<?>) this.annotations == (List<?>) emptyList()) {
this.annotations = new ArrayList<>(ANNOTATIONS_CONTAINER_DEFAULT_CAPACITY);
}
annotation.setParent(this);
this.annotations.add(annotation);
return (E) this;
}
public boolean removeAnnotation(CtAnnotation<? extends Annotation> annotation) {
return (List<?>) annotations != (List<?>) emptyList() && this.annotations.remove(annotation);
}
public <E extends CtElement> E setDocComment(String docComment) {
for (CtComment ctComment : comments) {
if (ctComment.getCommentType() == CtComment.CommentType.JAVADOC) {
ctComment.setContent(docComment);
return (E) this;
}
}
this.addComment(factory.Code().createComment(docComment, CtComment.CommentType.JAVADOC));
return (E) this;
}
public <E extends CtElement> E setPosition(SourcePosition position) {
this.position = position;
return (E) this;
}
public <E extends CtElement> E setPositions(final SourcePosition position) {
accept(new CtScanner() {
@Override
public void enter(CtElement e) {
e.setPosition(position);
}
});
return (E) this;
}
@Override
public String toString() {
DefaultJavaPrettyPrinter printer = new DefaultJavaPrettyPrinter(getFactory().getEnvironment());
String errorMessage = "";
try {
printer.computeImports(this);
printer.scan(this);
} catch (ParentNotInitializedException ignore) {
LOGGER.error(ERROR_MESSAGE_TO_STRING, ignore);
errorMessage = ERROR_MESSAGE_TO_STRING;
}
return printer.toString() + errorMessage;
}
@SuppressWarnings("unchecked")
public <E extends CtElement> List<E> getAnnotatedChildren(Class<? extends Annotation> annotationType) {
return (List<E>) Query.getElements(this, new AnnotationFilter<>(CtElement.class, annotationType));
}
boolean implicit = false;
public boolean isImplicit() {
return implicit;
}
public <E extends CtElement> E setImplicit(boolean implicit) {
this.implicit = implicit;
return (E) this;
}
public Set<CtTypeReference<?>> getReferencedTypes() {
TypeReferenceScanner s = new TypeReferenceScanner();
s.scan(this);
return s.getReferences();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <E extends CtElement> List<E> getElements(Filter<E> filter) {
return filterChildren(filter).list();
}
@Override
public <I> CtQuery map(CtConsumableFunction<I> queryStep) {
return factory.Query().createQuery(this).map(queryStep);
}
@Override
public <I, R> CtQuery map(CtFunction<I, R> function) {
return factory.Query().createQuery(this).map(function);
}
@Override
public <P extends CtElement> CtQuery filterChildren(Filter<P> predicate) {
return factory.Query().createQuery(this).filterChildren(predicate);
}
@Override
public CtElement getParent() throws ParentNotInitializedException {
if (parent == null) {
String exceptionMsg = "";
if (this instanceof CtReference) {
exceptionMsg = "parent not initialized for " + ((CtReference) this).getSimpleName() + "(" + this.getClass() + ")";
} else {
SourcePosition pos = getPosition();
if (this instanceof CtNamedElement) {
exceptionMsg = ("parent not initialized for " + ((CtNamedElement) this).getSimpleName() + "(" + this.getClass() + ")" + (pos != null ? " " + pos : " (?)"));
} else {
exceptionMsg = ("parent not initialized for " + this.getClass() + (pos != null ? " " + pos : " (?)"));
}
}
throw new ParentNotInitializedException(exceptionMsg);
}
return parent;
}
@Override
public <E extends CtElement> E setParent(E parent) {
this.parent = parent;
return (E) this;
}
@Override
public boolean isParentInitialized() {
return parent != null;
}
@Override
@SuppressWarnings("unchecked")
public <P extends CtElement> P getParent(Class<P> parentType) throws ParentNotInitializedException {
if (parent == null) {
return null;
}
if (parentType.isAssignableFrom(getParent().getClass())) {
return (P) getParent();
}
return getParent().getParent(parentType);
}
@Override
@SuppressWarnings("unchecked")
public <E extends CtElement> E getParent(Filter<E> filter) throws ParentNotInitializedException {
E current = (E) getParent();
while (true) {
try {
while (current != null && !filter.matches(current)) {
current = (E) current.getParent();
}
break;
} catch (ClassCastException e) {
// expected, some elements are not of type
current = (E) current.getParent();
}
}
if (current != null && filter.matches(current)) {
return current;
}
return null;
}
@Override
public boolean hasParent(CtElement candidate) {
try {
return this != getFactory().getModel().getRootPackage() && (getParent() == candidate || getParent().hasParent(candidate));
} catch (ParentNotInitializedException e) {
return false;
}
}
@Override
public void updateAllParentsBelow() {
new ModelConsistencyChecker(getFactory().getEnvironment(), true, true).scan(this);
}
@Override
public Factory getFactory() {
return factory;
}
@Override
public void setFactory(Factory factory) {
this.factory = factory;
LOGGER.setLevel(factory.getEnvironment().getLevel());
}
@Override
public void replace(CtElement element) {
ReplacementVisitor.replace(this, element);
}
@Override
public <E extends CtElement> E putMetadata(String key, Object val) {
if (metadata == null) {
metadata = new HashMap<>();
}
metadata.put(key, val);
return (E) this;
}
@Override
public Object getMetadata(String key) {
if (metadata == null) {
return null;
}
return metadata.get(key);
}
@Override
public Set<String> getMetadataKeys() {
if (metadata == null) {
return Collections.EMPTY_SET;
}
return metadata.keySet();
}
@Override
public List<CtComment> getComments() {
return unmodifiableList(comments);
}
@Override
public <E extends CtElement> E addComment(CtComment comment) {
if (comment == null) {
return (E) this;
}
if ((List<?>) comments == emptyList()) {
comments = new ArrayList<>(COMMENT_CONTAINER_DEFAULT_CAPACITY);
}
comments.add(comment);
comment.setParent(this);
return (E) this;
}
// TODO return boolean
@Override
public <E extends CtElement> E removeComment(CtComment comment) {
if ((List<?>) comments != emptyList()) {
comments.remove(comment);
}
return (E) this;
}
@Override
public <E extends CtElement> E setComments(List<CtComment> comments) {
if (comments == null || comments.isEmpty()) {
this.comments = CtElementImpl.emptyList();
return (E) this;
}
this.comments.clear();
for (CtComment comment : comments) {
addComment(comment);
}
return (E) this;
}
@Override
public CtElement clone() {
return CloneHelper.clone(this);
}
}