org.jboss.forge.roaster.model.impl.JavaSourceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.forge.roaster.model.impl.JavaSourceImpl.java

Source

/*
 * Copyright 2018 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.forge.roaster.model.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.TextEdit;
import org.jboss.forge.roaster.ParserException;
import org.jboss.forge.roaster.model.Annotation;
import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.SyntaxError;
import org.jboss.forge.roaster.model.Type;
import org.jboss.forge.roaster.model.Visibility;
import org.jboss.forge.roaster.model.ast.AnnotationAccessor;
import org.jboss.forge.roaster.model.ast.ModifierAccessor;
import org.jboss.forge.roaster.model.source.AnnotationSource;
import org.jboss.forge.roaster.model.source.Import;
import org.jboss.forge.roaster.model.source.JavaDocSource;
import org.jboss.forge.roaster.model.source.JavaSource;
import org.jboss.forge.roaster.model.util.impl.Formatter;
import org.jboss.forge.roaster.model.util.impl.JDTOptions;
import org.jboss.forge.roaster.model.util.Types;
import org.jboss.forge.roaster.spi.WildcardImportResolver;

/**
 * Represents a Java Source File
 *
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 */
public abstract class JavaSourceImpl<O extends JavaSource<O>> implements JavaSource<O> {
    private static List<WildcardImportResolver> resolvers;
    private final ModifierAccessor modifiers = new ModifierAccessor();
    private final AnnotationAccessor<O, O> annotations = new AnnotationAccessor<>();

    protected final Document document;
    protected final CompilationUnit unit;
    protected final JavaSource<?> enclosingType;

    protected JavaSourceImpl(JavaSource<?> enclosingType, final Document document, final CompilationUnit unit) {
        this.enclosingType = enclosingType == null ? this : enclosingType;
        this.document = document;
        this.unit = unit;
    }

    // ================================================================================
    // Location
    // ================================================================================

    @Override
    public int getColumnNumber() {
        return unit.getColumnNumber(getStartPosition());
    }

    @Override
    public int getLineNumber() {
        return unit.getLineNumber(getStartPosition());
    }

    @Override
    public int getEndPosition() {
        int startPosition = getStartPosition();
        return (startPosition == -1) ? -1 : startPosition + getDeclaration().getLength();
    }

    @Override
    public int getStartPosition() {
        return getDeclaration().getStartPosition();
    }

    // ================================================================================
    // JavaDoc
    // ================================================================================

    @Override
    public boolean hasJavaDoc() {
        return getJDTJavaDoc() != null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public JavaDocSource<O> getJavaDoc() {
        Javadoc javadoc = getJDTJavaDoc();
        if (javadoc == null) {
            javadoc = getDeclaration().getAST().newJavadoc();
            setJDTJavaDoc(javadoc);
        }
        return new JavaDocImpl<>((O) this, javadoc);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O removeJavaDoc() {
        setJDTJavaDoc(null);
        return (O) this;
    }

    protected abstract Javadoc getJDTJavaDoc();

    protected abstract void setJDTJavaDoc(Javadoc javaDoc);

    // ================================================================================
    // Annotation
    // ================================================================================

    @Override
    public AnnotationSource<O> addAnnotation() {
        return annotations.addAnnotation(this, getDeclaration());
    }

    @Override
    public List<AnnotationSource<O>> getAnnotations() {
        return annotations.getAnnotations(this, getDeclaration());
    }

    @Override
    public AnnotationSource<O> addAnnotation(final Class<? extends java.lang.annotation.Annotation> clazz) {
        return annotations.addAnnotation(this, getDeclaration(), clazz.getName());
    }

    @Override
    public AnnotationSource<O> addAnnotation(final String className) {
        return annotations.addAnnotation(this, getDeclaration(), className);
    }

    @Override
    public boolean hasAnnotation(final Class<? extends java.lang.annotation.Annotation> type) {
        return annotations.hasAnnotation(this, getDeclaration(), type.getName());
    }

    @Override
    public boolean hasAnnotation(final String type) {
        return annotations.hasAnnotation(this, getDeclaration(), type);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O removeAnnotation(final Annotation<O> annotation) {
        return (O) annotations.removeAnnotation(this, getDeclaration(), annotation);
    }

    @Override
    public void removeAllAnnotations() {
        annotations.removeAllAnnotations(getDeclaration());
    }

    @Override
    public AnnotationSource<O> getAnnotation(final Class<? extends java.lang.annotation.Annotation> type) {
        return annotations.getAnnotation(this, getDeclaration(), type);
    }

    @Override
    public AnnotationSource<O> getAnnotation(final String type) {
        return annotations.getAnnotation(this, getDeclaration(), type);
    }

    // ================================================================================
    // Import
    // ================================================================================

    @Override
    public Import addImport(final Class<?> type) {
        return addImport(type.getCanonicalName());
    }

    @Override
    public <T extends JavaType<?>> Import addImport(final T type) {
        String qualifiedName = type.getQualifiedName();
        return this.addImport(qualifiedName);
    }

    @Override
    public Import addImport(final Import imprt) {
        return addImport(imprt.getQualifiedName()).setStatic(imprt.isStatic());
    }

    @SuppressWarnings("unchecked")
    @Override
    public Import addImport(final String className) {
        String strippedClassName = Types.stripArray(className);

        if (Types.isGeneric(className)) {
            for (String genericPart : Types.splitGenerics(className)) {
                // a type variable is not qualified, so it won't be imported by accident
                if (Types.isQualified(genericPart))
                    addImport(genericPart);
            }
        }

        // test this after generics are imported
        strippedClassName = Types.stripGenerics(strippedClassName);
        if (!Types.isQualified(strippedClassName) || Types.isJavaLang(strippedClassName)) {
            return null;
        }

        if (!hasImport(strippedClassName) && validImport(strippedClassName)) {
            Import imprt = new ImportImpl(this).setName(strippedClassName);
            unit.imports().add(imprt.getInternal());
            return imprt;
        }

        if (hasImport(strippedClassName)) {
            return getImport(strippedClassName);
        }

        return null;
    }

    @Override
    public Import getImport(final String className) {
        String strippedClassName = Types.stripArray(Types.stripGenerics(className));

        for (Import imprt : getImports()) {
            String qualifiedName = imprt.getQualifiedName();
            if (imprt.isWildcard()) {
                qualifiedName = qualifiedName.substring(0, qualifiedName.length() - 2); // remove .*
            }

            if (qualifiedName.equals(strippedClassName)) {
                return imprt;
            }
        }
        return null;
    }

    @Override
    public Import addImport(final Type<?> type) {
        Import imprt;
        if (requiresImport(type.getQualifiedName())) {
            imprt = addImport(type.getQualifiedName());
        } else {
            imprt = getImport(type.getQualifiedName());
        }
        for (Type<?> arg : type.getTypeArguments()) {
            if (!arg.isWildcard() && arg.isQualified()) {
                addImport(arg);
            }
        }
        return imprt;
    }

    @Override
    public Import getImport(final Class<?> type) {
        return getImport(type.getName());
    }

    @Override
    public <T extends JavaType<?>> Import getImport(final T type) {
        return getImport(type.getQualifiedName());
    }

    @Override
    public Import getImport(final Import imprt) {
        return getImport(imprt.getQualifiedName());
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Import> getImports() {
        List<Import> results = new ArrayList<>();

        for (ImportDeclaration i : (List<ImportDeclaration>) unit.imports()) {
            results.add(new ImportImpl(this, i));
        }

        return Collections.unmodifiableList(results);
    }

    @Override
    public boolean hasImport(final Class<?> type) {
        return hasImport(type.getName());
    }

    @Override
    public <T extends JavaType<T>> boolean hasImport(final T type) {
        return hasImport(type.getQualifiedName());
    }

    @Override
    public boolean hasImport(final Import imprt) {
        return hasImport(imprt.getQualifiedName());
    }

    @Override
    public boolean hasImport(final String type) {
        String resultType = type;
        if (Types.isArray(type)) {
            resultType = Types.stripArray(type);
        }
        if (Types.isGeneric(type)) {
            resultType = Types.stripGenerics(type);
        }
        return getImport(resultType) != null;
    }

    @Override
    public boolean requiresImport(final Class<?> type) {
        return requiresImport(type.getName());
    }

    @Override
    public boolean requiresImport(final String type) {
        boolean requiresImport = false;
        String resultType = type;
        if (Types.isArray(resultType)) {
            resultType = Types.stripArray(resultType);
        }
        if (Types.isGeneric(resultType)) {
            for (String genericPart : Types.splitGenerics(resultType)) {
                requiresImport |= requiresImport(genericPart);
            }
            resultType = Types.stripGenerics(resultType);
        }
        requiresImport |= !(!validImport(resultType) || hasImport(resultType) || Types.isJavaLang(resultType)
                || Objects.equals(getPackage(), Types.getPackage(resultType)));
        return requiresImport;
    }

    @Override
    public String resolveType(final String type) {
        String result = type;

        // Strip away any characters that might hinder the type matching process
        if (Types.isArray(result)) {
            result = Types.stripArray(result);
        }

        if (Types.isGeneric(result)) {
            result = Types.stripGenerics(result);
        }

        // primitive types don't need a import -> direct return
        if (Types.isPrimitive(result)) {
            return result;
        }

        // qualified names don't need to be resolved
        if (Types.isQualified(result)) {
            return result;
        }

        // java lang types are implicitly imported and we don't allow a duplicate simple name in another package
        if (Types.isJavaLang(result)) {
            return "java.lang." + result;
        }

        List<Import> imports = getImports(); // fetch imports only once

        // no need to check for a import with getImport becuase than a fqn name is needed
        // search for an existing import with the same simple name
        for (Import imprt : imports) {
            if (imprt.getSimpleName().equals(result)) {
                return imprt.getQualifiedName();
            }
        }

        // if we have no imports and no fqn name the following doesn't need to be executed
        if (!imports.isEmpty()) {
            // if we have only one wildcard import we can use this
            List<Import> wildcardImports = imports.stream().filter(Import::isWildcard).collect(Collectors.toList());
            if (wildcardImports.size() == 1) {
                return wildcardImports.get(0).getPackage() + "." + result;
            }

            // If we didn't match any imports directly, we might have a wild-card/on-demand import.
            for (Import imprt : imports) {
                if (imprt.isWildcard()) {
                    // TODO warn if no wild-card resolvers are configured
                    // TODO Test wild-card/on-demand import resolving
                    for (WildcardImportResolver r : getImportResolvers()) {
                        result = r.resolve(this, result);
                        if (Types.isQualified(result))
                            return result;
                    }
                }
            }
        }

        // Nothing matched since here, so try to resolve it with the current package
        if (getPackage() != null) {
            return getPackage() + "." + result;
        }

        return result;
    }

    private List<WildcardImportResolver> getImportResolvers() {
        if (resolvers == null) {
            resolvers = new ArrayList<>();
            for (WildcardImportResolver r : ServiceLoader.load(WildcardImportResolver.class,
                    getClass().getClassLoader())) {
                resolvers.add(r);
            }
        }
        if (resolvers.isEmpty()) {
            throw new IllegalStateException("No instances of [" + WildcardImportResolver.class.getName()
                    + "] were found on the classpath.");
        }
        return resolvers;
    }

    private boolean validImport(final String type) {
        String className = Types.toSimpleName(type);
        // check if this class name is equal to the class to import
        if (className.equals(getName())) {
            return false;
        }
        // check if any import has the same class name
        for (final Import imprt : getImports()) {
            String importClassName = imprt.getSimpleName();
            if (!imprt.isWildcard() && importClassName.equals(className)) {
                return false;
            }
        }
        return !StringUtils.isEmpty(type) && !Types.isPrimitive(type)
                && !StringUtils.isEmpty(Types.getPackage(type));
    }

    @SuppressWarnings("unchecked")
    @Override
    public O removeImport(final String name) {
        for (Import i : getImports()) {
            if (i.getQualifiedName().equals(name)) {
                removeImport(i);
                break;
            }
        }
        return (O) this;
    }

    @Override
    public O removeImport(final Class<?> clazz) {
        return removeImport(clazz.getName());
    }

    @Override
    public <T extends JavaType<?>> O removeImport(final T type) {
        return removeImport(type.getQualifiedName());
    }

    @SuppressWarnings("unchecked")
    @Override
    public O removeImport(final Import imprt) {
        Object internal = imprt.getInternal();
        if (unit.imports().contains(internal)) {
            unit.imports().remove(internal);
        }
        return (O) this;
    }

    // ================================================================================
    // Naming
    // ================================================================================

    @Override
    public String getCanonicalName() {
        String result = getName();

        JavaType<?> enclosingTypeLocal = this;
        while (enclosingTypeLocal != enclosingTypeLocal.getEnclosingType()) {
            enclosingTypeLocal = enclosingTypeLocal.getEnclosingType();
            result = enclosingTypeLocal.getName() + "." + result;
        }

        if (!StringUtils.isEmpty(getPackage()))
            result = getPackage() + "." + result;

        return result;
    }

    /**
     * Call-back to allow updating of any necessary internal names with the given name.
     */
    protected abstract O updateTypeNames(String name);

    @Override
    public String getQualifiedName() {
        String result = getName();

        JavaType<?> enclosingTypeLocal = this;
        while (enclosingTypeLocal != enclosingTypeLocal.getEnclosingType()) {
            enclosingTypeLocal = enclosingTypeLocal.getEnclosingType();
            result = enclosingTypeLocal.getName() + "$" + result;
        }

        if (!StringUtils.isEmpty(getPackage()))
            result = getPackage() + "." + result;

        return result;
    }

    // ================================================================================
    // Package
    // ================================================================================

    @Override
    public String getPackage() {
        PackageDeclaration pkg = unit.getPackage();
        if (pkg != null) {
            return pkg.getName().getFullyQualifiedName();
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setPackage(final String name) {
        if (unit.getPackage() == null) {
            unit.setPackage(unit.getAST().newPackageDeclaration());
        }
        unit.getPackage().setName(unit.getAST().newName(name));
        return (O) this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setDefaultPackage() {
        unit.setPackage(null);
        return (O) this;
    }

    @Override
    public boolean isDefaultPackage() {
        return unit.getPackage() == null;
    }

    // ================================================================================
    // String methods
    // ================================================================================
    /**
     * Return this {@link JavaType} file as a String
     */
    @Override
    public String toString() {
        return Formatter.format(toUnformattedString());
    }

    @Override
    public String toUnformattedString() {
        Document documentLocal = new Document(this.document.get());

        try {
            Map<String, String> options = JDTOptions.getJDTOptions();
            TextEdit edit = unit.rewrite(documentLocal, options);
            edit.apply(documentLocal);
        } catch (Exception e) {
            throw new ParserException("Could not modify source: " + unit.toString(), e);
        }

        return documentLocal.get();
    }

    // ================================================================================
    // Visibility
    // ================================================================================

    @SuppressWarnings("unchecked")
    @Override
    public O setPackagePrivate() {
        modifiers.clearVisibility(getDeclaration());
        return (O) this;
    }

    @Override
    public boolean isPublic() {
        return modifiers.hasModifier(getDeclaration(), ModifierKeyword.PUBLIC_KEYWORD);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setPublic() {
        modifiers.clearVisibility(getDeclaration());
        modifiers.addModifier(getDeclaration(), ModifierKeyword.PUBLIC_KEYWORD);
        return (O) this;
    }

    @Override
    public boolean isPrivate() {
        return modifiers.hasModifier(getDeclaration(), ModifierKeyword.PRIVATE_KEYWORD);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setPrivate() {
        modifiers.clearVisibility(getDeclaration());
        modifiers.addModifier(getDeclaration(), ModifierKeyword.PRIVATE_KEYWORD);
        return (O) this;
    }

    @Override
    public boolean isProtected() {
        return modifiers.hasModifier(getDeclaration(), ModifierKeyword.PROTECTED_KEYWORD);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setProtected() {
        modifiers.clearVisibility(getDeclaration());
        modifiers.addModifier(getDeclaration(), ModifierKeyword.PROTECTED_KEYWORD);
        return (O) this;
    }

    @Override
    public boolean isPackagePrivate() {
        return !isPublic() && !isPrivate() && !isProtected();
    }

    @Override
    public Visibility getVisibility() {
        return Visibility.getFrom(this);
    }

    @SuppressWarnings("unchecked")
    @Override
    public O setVisibility(final Visibility scope) {
        return (O) Visibility.set(this, scope);
    }

    // ================================================================================
    // Types
    // ================================================================================

    @Override
    public boolean isClass() {
        ASTNode declaration = getDeclaration();
        return (declaration instanceof TypeDeclaration) && !((TypeDeclaration) declaration).isInterface();

    }

    @Override
    public boolean isEnum() {
        ASTNode declaration = getDeclaration();
        return declaration instanceof EnumDeclaration;
    }

    @Override
    public boolean isInterface() {
        ASTNode declaration = getDeclaration();
        return (declaration instanceof TypeDeclaration) && ((TypeDeclaration) declaration).isInterface();
    }

    @Override
    public boolean isAnnotation() {
        return getDeclaration() instanceof AnnotationTypeDeclaration;
    }

    // ================================================================================
    // other
    // ================================================================================

    @Override
    public JavaSource<?> getEnclosingType() {
        return enclosingType;
    }

    @Override
    public int hashCode() {
        if (enclosingType == this) {
            return Objects.hash(document, unit);
        }
        return Objects.hash(document, enclosingType, unit);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        AbstractJavaSource<?> other = (AbstractJavaSource<?>) obj;
        if (document == null) {
            if (other.document != null)
                return false;
        } else if (!document.equals(other.document))
            return false;
        if (enclosingType == null) {
            if (other.enclosingType != null)
                return false;
        } else if (!enclosingType.equals(other.enclosingType))
            return false;
        if (unit == null) {
            if (other.unit != null)
                return false;
        } else if (!unit.equals(other.unit))
            return false;
        return true;
    }

    @Override
    public Object getInternal() {
        return unit;
    }

    @SuppressWarnings("unchecked")
    @Override
    public O getOrigin() {
        return (O) this;
    }

    @Override
    public List<SyntaxError> getSyntaxErrors() {
        List<SyntaxError> result = new ArrayList<>();

        IProblem[] problems = unit.getProblems();
        if (problems != null) {
            for (IProblem problem : problems) {
                result.add(new SyntaxErrorImpl(this, problem));
            }
        }
        return result;
    }

    @Override
    public boolean hasSyntaxErrors() {
        return !getSyntaxErrors().isEmpty();
    }

    protected abstract ASTNode getDeclaration();
}