All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.faktorips.codegen.JavaCodeFragment Maven / Gradle / Ivy

There is a newer version: 25.1.0.a20241030-01
Show newest version
/*******************************************************************************
 * Copyright (c) Faktor Zehn GmbH - faktorzehn.org
 * 
 * This source code is available under the terms of the AGPL Affero General Public License version
 * 3.
 * 
 * Please see LICENSE.txt for full license terms, including the additional permissions and
 * restrictions as well as the possibility of alternative license terms.
 *******************************************************************************/

package org.faktorips.codegen;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

import org.faktorips.runtime.util.StringBuilderJoiner;
import org.faktorips.util.StringUtil;

/**
 * The class represents a Java source code fragment. A source code fragment consists of the source
 * code text and the import statements needed to compile the text.
 * 
 * @see JavaCodeFragmentBuilder
 * @see ImportDeclaration
 */
public class JavaCodeFragment extends CodeFragment {

    // import declaration needed to compile the source code
    private ImportDeclaration importDecl;

    /**
     * Creates a new empty JavaCodeFragment.
     */
    public JavaCodeFragment() {
        this(false);
    }

    public JavaCodeFragment(boolean indent) {
        super(indent);
        importDecl = new ImportDeclaration();
    }

    /**
     * Creates a new JavaCodeFragment with the indicated source code and no import statements.
     */
    public JavaCodeFragment(String sourcecode) {
        this(sourcecode, new ImportDeclaration());
    }

    /**
     * Creates a new JavaCodeFragment with the indicated source code and import declaration.
     */
    public JavaCodeFragment(String sourcecode, ImportDeclaration importDecl) {
        super(sourcecode);
        this.importDecl = new ImportDeclaration(importDecl);
    }

    /**
     * Constructs a JavaCodeFragment that has the same source code as the given fragment with all
     * import statements removed that are obsolete because of the indicated package.
     */
    public JavaCodeFragment(JavaCodeFragment fragment, String packageName) {
        super(fragment);
        importDecl = new ImportDeclaration(fragment.importDecl, packageName);
    }

    /**
     * Copy constructor.
     */
    public JavaCodeFragment(JavaCodeFragment fragment) {
        this(fragment.getSourcecode(), new ImportDeclaration(fragment.getImportDeclaration()));
    }

    /**
     * Returns the import declaration needed to compile the source code.
     */
    public ImportDeclaration getImportDeclaration() {
        // defensive copy
        return new ImportDeclaration(importDecl);
    }

    /**
     * Returns the import declaration needed to compile the source code. The returned import
     * declaration does not contain any import statements that refer to the indicated packageName.
     * The method is useful to avoid unnecessary import statements.
     */
    public ImportDeclaration getImportDeclaration(String packageName) {
        return new ImportDeclaration(importDecl, packageName);
    }

    /**
     * Appends '{' to the source code and increases the indentation level.
     */
    public JavaCodeFragment appendOpenBracket() {
        appendln('{');
        incIndentationLevel();
        return this;
    }

    /**
     * Appends '}' to the source code and decreases the indentation level.
     */
    public JavaCodeFragment appendCloseBracket() {
        appendln('}');
        decIndentationLevel();
        return this;
    }

    @Override
    public JavaCodeFragment append(String s) {
        return (JavaCodeFragment)super.append(s);
    }

    @Override
    public JavaCodeFragment appendJoined(Iterable iterable) {
        return (JavaCodeFragment)super.appendJoined(iterable);
    }

    @Override
    public JavaCodeFragment appendJoined(Object[] array) {
        return (JavaCodeFragment)super.appendJoined(array);
    }

    public JavaCodeFragment appendJoined(JavaCodeFragment[] javaCodeFragments) {
        boolean first = true;
        for (JavaCodeFragment javaCodeFragment : javaCodeFragments) {
            if (!first) {
                append(StringBuilderJoiner.DEFAULT_SEPARATOR);
            }
            append(javaCodeFragment);
            first = false;
        }
        return this;
    }

    @Override
    public JavaCodeFragment appendQuoted(String s) {
        return (JavaCodeFragment)super.appendQuoted(s);
    }

    @Override
    public JavaCodeFragment append(char c) {
        return (JavaCodeFragment)super.append(c);
    }

    @Override
    public JavaCodeFragment appendln() {
        return (JavaCodeFragment)super.appendln();
    }

    @Override
    public JavaCodeFragment appendln(String s) {
        return (JavaCodeFragment)super.appendln(s);
    }

    @Override
    public JavaCodeFragment appendlnUnindented(String arg) {
        return (JavaCodeFragment)super.appendlnUnindented(arg);
    }

    @Override
    public JavaCodeFragment appendln(char c) {
        return (JavaCodeFragment)super.appendln(c);
    }

    /**
     * Transform the given int into a String and appends it to the source code.
     */
    public JavaCodeFragment append(int i) {
        append("" + i); //$NON-NLS-1$
        return this;
    }

    /**
     * Transform the given boolean into a String and appends it to the source code.
     */
    public JavaCodeFragment append(boolean b) {
        append("" + b); //$NON-NLS-1$
        return this;
    }

    /**
     * Appends the unqualified class name to the source code and updates the import declaration (if
     * necessary).
     * 
     * @throws NullPointerException if clazz is null.
     */
    public JavaCodeFragment appendClassName(Class clazz) {
        if (clazz.isArray()) {
            appendClassName(clazz.getComponentType());
            append("[]"); //$NON-NLS-1$
            return this;
        }
        appendClassName(clazz.getName());
        return this;
    }

    /**
     * Appends the unqualified class name of an public inner class to the source code and updates
     * the import declaration (if necessary).
     * 
     * @throws NullPointerException if clazz is null.
     */
    public JavaCodeFragment appendInnerClassName(Class clazz) {
        appendInnerClassName(clazz.getName());
        return this;
    }

    /**
     * Appends the unqualified class name of an public inner class to the source code and updates
     * the import declaration (if necessary).
     * 
     * @throws NullPointerException if qualifiedClassName is null.
     */
    public JavaCodeFragment appendInnerClassName(String qualifiedClassName) {
        appendClassName(qualifiedClassName.replace('$', '.'));
        return this;
    }

    /**
     * Appends the unqualified class name to the source code and updates the import declaration (if
     * necessary). A [] appended at the end of the qualified class name indicates the array of this
     * type. The brackets are added correctly at the end of the class name in the source code.
     * 
     * @throws NullPointerException if qualifiedClassName is null.
     */
    public JavaCodeFragment appendClassName(String qualifiedClassName) {
        if (qualifiedClassName.contains("<")) { //$NON-NLS-1$
            return appendClassNameAndParseGenerics(qualifiedClassName);
        } else {
            return appendClassNameWithoutGenerics(qualifiedClassName);
        }
    }

    private JavaCodeFragment appendClassNameAndParseGenerics(String qualifiedClassName) {
        appendClassNameWithoutGenerics(qualifiedClassName.substring(0, qualifiedClassName.indexOf('<')));
        parseGenerics(qualifiedClassName);
        return this;
    }

    private void parseGenerics(String qualifiedClassName) {
        String generics = qualifiedClassName.substring(qualifiedClassName.indexOf('<') + 1,
                qualifiedClassName.lastIndexOf('>'));
        List toplevelGenericTypes = splitToplevelGenericTypesByColon(generics);
        append('<');
        for (Iterator iterator = toplevelGenericTypes.iterator(); iterator.hasNext();) {
            String nextGenericType = iterator.next();
            // Recursion: also handles nested generics.
            appendClassName(nextGenericType);
            if (iterator.hasNext()) {
                append(", "); //$NON-NLS-1$
            }
        }
        append('>');
    }

    List splitToplevelGenericTypesByColon(String genericsDefinition) {
        LinkedList result = new LinkedList<>();
        int depth = 0;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < genericsDefinition.length(); i++) {
            char c = genericsDefinition.charAt(i);
            if (c == '<') {
                depth++;
            } else if (c == '>') {
                depth--;
            } else if (c == ',' && depth == 0) {
                result.add(sb.toString().trim());
                sb = new StringBuilder();
                continue;
            }
            sb.append(c);
        }
        result.add(sb.toString().trim());
        return result;
    }

    private JavaCodeFragment appendClassNameWithoutGenerics(final String className) {
        // Consider "$" for inner classes
        String qualifiedClassName = className.replace('$', '.');
        String unqualifiedClassName = StringUtil.unqualifiedName(qualifiedClassName);
        // don't add two imports for the same unqualified name
        for (Iterator iterator = importDecl.iterator(); iterator.hasNext();) {
            String imp = iterator.next();
            if (imp.substring(imp.lastIndexOf('.') + 1).equals(unqualifiedClassName)
                    && !imp.equals(qualifiedClassName)) {
                append(qualifiedClassName);
                return this;
            }
        }
        append(unqualifiedClassName);
        if (qualifiedClassName.indexOf('.') < 0) {
            return this;
        }
        int bracketIndex = qualifiedClassName.indexOf("[]"); //$NON-NLS-1$
        if (bracketIndex > -1) {
            importDecl.add(qualifiedClassName.substring(0, bracketIndex));
        } else {
            importDecl.add(qualifiedClassName);
        }
        return this;
    }

    /**
     * Adds an import entry to this code fragment.
     * 
     * @param qualifiedClassName the java class that is added to the import declaration
     */
    public JavaCodeFragment addImport(String qualifiedClassName) {
        importDecl.add(qualifiedClassName);
        return this;
    }

    /**
     * Appends the given fragment to his fragment and indents it properly.
     */
    @Override
    public JavaCodeFragment append(CodeFragment fragment) {
        return append((JavaCodeFragment)fragment);
    }

    /**
     * Appends the given fragment to his fragment and indents it properly.
     */
    public JavaCodeFragment append(JavaCodeFragment fragment) {
        importDecl.add(fragment.getImportDeclaration());
        super.append(fragment);
        return this;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        return prime * result + ((importDecl == null) ? 0 : importDecl.hashCode());
    }

    /**
     * Two fragments are equal if they contain the same source code and have the same import
     * declaration.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if ((obj == null) || (getClass() != obj.getClass())) {
            return false;
        }
        JavaCodeFragment other = (JavaCodeFragment)obj;
        if (!super.equals(obj)) {
            return false;
        }
        return Objects.equals(importDecl, other.importDecl);
    }

    /**
     * Returns the CodeFragment as String in "normal" Java style, that means first all import
     * statements, new line, than the source code.
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return importDecl.toString() + System.lineSeparator() + super.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy