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

guru.nidi.codeassert.model.SignatureParser Maven / Gradle / Ivy

There is a newer version: 0.9.15
Show newest version
/*
 * Copyright © 2015 Stefan Niederhauser ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package guru.nidi.codeassert.model;

import guru.nidi.codeassert.AnalyzerException;

import java.util.*;

/**
 * Parse a java type signature.
 * see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
 */
final class SignatureParser {
    public enum Source {
        CLASS, FIELD, METHOD
    }

    private static final char EOF = (char) -1;
    private static final String NOT_IDENT = ".;[/<>:";
    private static final String BASE_TYPES = "BCDFIJSZ";

    private final String s;
    private char c;
    private int pos;
    private final Set classes = new HashSet<>();

    private SignatureParser(String s) {
        this.s = s;
        pos = 0;
        read();
    }

    public static SignatureParser parseSignature(Source source, String signature) {
        final SignatureParser parser = new SignatureParser(signature);
        switch (source) {
            case CLASS:
                parser.classSignature();
                break;
            case FIELD:
                parser.fieldTypeSignature(false);
                break;
            case METHOD:
                parser.methodTypeSignature();
                break;
            default:
                //nothing
        }
        return parser;
    }

    public Collection getClasses() {
        return classes;
    }

    private void classSignature() {
        if (is('<')) {
            formalTypeParameters();
        }
        do {
            classTypeSignature();
        } while (!is(EOF));
    }

    private void formalTypeParameters() {
        read('<');
        do {
            formalTypeParameter();
        } while (!is('>'));
        read('>');
    }

    private void formalTypeParameter() {
        identifier();
        classBound();
        while (is(':')) {
            interfaceBound();
        }
    }

    private void classBound() {
        read(':');
        fieldTypeSignature(true);
    }

    private void interfaceBound() {
        read(':');
        fieldTypeSignature(false);
    }

    private void fieldTypeSignature(boolean opt) {
        if (!classTypeOrTypeVariableSignature()) {
            if (is('[')) {
                arrayTypeSignature();
            } else if (!opt) {
                throw new AnalyzerException("FieldTypeSignature expected [" + s + "]:" + pos);
            }
        }
    }

    private void classTypeSignature() {
        read('L');
        final StringBuilder s = new StringBuilder();
        s.append(classIdentifier());
        while (!is(';') && !is('<')) {
            if (is('$')) {
                read();
                classIdentifier();
            } else {
                s.append('.');
                read();
                s.append(classIdentifier());
            }
        }
        final String id = s.toString();
        if (is('<')) {
            typeArguments();
        }
        while (is('.')) {
            classTypeSignatureSuffix();
        }
        classes.add(id);
        read(';');
    }

    private void classTypeSignatureSuffix() {
        read('.');
        classIdentifier();
        if (is('<')) {
            typeArguments();
        }
    }

    private void typeArguments() {
        read('<');
        do {
            typeArgument();
        } while (!is('>'));
        read('>');
    }

    private void typeArgument() {
        if (is('*')) {
            read('*');
        } else {
            if (is('+')) {
                read('+');
            } else if (is('-')) {
                read('-');
            }
            fieldTypeSignature(false);
        }
    }

    private void arrayTypeSignature() {
        read('[');
        typeSignature();
    }

    private void typeSignature() {
        if (isBaseType()) {
            read();
        } else {
            fieldTypeSignature(false);
        }
    }

    private boolean isBaseType() {
        return BASE_TYPES.indexOf(c) >= 0;
    }

    private void typeVariableSignature() {
        read('T');
        identifier();
        read(';');
    }

    private void methodTypeSignature() {
        if (is('<')) {
            formalTypeParameters();
        }
        read('(');
        while (!is(')')) {
            typeSignature();
        }
        read(')');
        returnType();
        while (is('^')) {
            throwsSignature();
        }
    }

    private void throwsSignature() {
        read('^');
        if (!classTypeOrTypeVariableSignature()) {
            throw new AnalyzerException("ClassType or TypeVariable signature expected [" + s + "]:" + pos);
        }
    }

    private boolean classTypeOrTypeVariableSignature() {
        if (is('L')) {
            classTypeSignature();
            return true;
        }
        if (is('T')) {
            typeVariableSignature();
            return true;
        }
        return false;
    }

    private void returnType() {
        if (is('V')) {
            read();
        } else {
            typeSignature();
        }
    }

    private String classIdentifier() {
        return identifier(true);
    }

    private String identifier() {
        return identifier(false);
    }

    private String identifier(boolean clazz) {
        final StringBuilder s = new StringBuilder();
        do {
            s.append(c);
            read();
        } while (NOT_IDENT.indexOf(c) < 0 && (!clazz || c != '$'));
        return s.toString();
    }

    private boolean is(char ch) {
        return c == ch;
    }

    private char read() {
        return c = (pos == s.length() ? EOF : s.charAt(pos++));
    }

    private char read(char ch) {
        if (c != ch) {
            throw new AnalyzerException("'" + ch + "' expected in '" + s + "':" + pos);
        }
        return read();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy