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

sk.uniq.protobuf.parser.impl.ProtoFileVisitor Maven / Gradle / Ivy

The newest version!
/* 
 * Copyright 2016 Jakub Herkel.
 *
 * 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 sk.uniq.protobuf.parser.impl;

import sk.uniq.protobuf.schema.ProtoRpc;
import sk.uniq.protobuf.schema.ProtoImport;
import sk.uniq.protobuf.schema.ProtoService;
import sk.uniq.protobuf.schema.ProtoPosition;
import sk.uniq.protobuf.schema.ProtoEnum;
import sk.uniq.protobuf.schema.ProtoOption;
import sk.uniq.protobuf.schema.ProtoMessage;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import sk.uniq.protobuf.grammar.ProtoBaseVisitor;
import sk.uniq.protobuf.grammar.ProtoParser;
import sk.uniq.protobuf.parser.ProtoParserOptions;
import sk.uniq.protobuf.schema.*;
import sk.uniq.protobuf.schema.ProtoVersion.Version;
import sk.uniq.protobuf.schema.impl.*;
import static sk.uniq.protobuf.parser.impl.ProtoUtils.*;

/**
 *
 * @author jherkel
 */
class ProtoFileVisitor extends ProtoBaseVisitor {

    private final ProtoFileImpl.Builder pfBuilder;
    private final ProtoParserImpl parser;
    private final ErrorListener errorListener;
    private final BufferedTokenStream tokenStream;

    /**
     *
     * @param filename
     * @param parser
     */
    public ProtoFileVisitor(String filename, ProtoParserImpl parser, BufferedTokenStream tokenStream, ErrorListener errorListener) {
        pfBuilder = ProtoFileImpl.builder().filename(filename);
        this.errorListener = errorListener;
        this.parser = parser;
        this.tokenStream = tokenStream;
    }

    @Override
    public Object visitProto(ProtoParser.ProtoContext ctx) {
        List elements = convertResultToList(super.visitProto(ctx));
        for (Object obj : elements) {
            if (obj instanceof ProtoSyntaxElement) {
                pfBuilder.addNestedElement((ProtoSyntaxElement) obj);
            } else {
                throw new IllegalStateException("Unknown element obj:" + obj.getClass().getName());
            }
        }
        return pfBuilder.build();
    }

    @Override
    public ProtoImport visitImportPB(ProtoParser.ImportPBContext ctx) {
        ProtoImport.Scope scope = ctx.WEAK_LITERAL() != null ? ProtoImport.Scope.WEAK : ProtoImport.Scope.PUBLIC;
        ProtoImport importPB = ProtoImportImpl.builder().position(position(ctx)).scope(scope).protoFile(ctx.protoFile.getText()).build();
        return importPB;
    }

    @Override
    public Object visitConstant(ProtoParser.ConstantContext ctx) {
        try {
            if (ctx.BOOLEAN_LITERAL() != null) {
                return ProtoBoolean.parse(ctx.BOOLEAN_LITERAL().getText());
            } else if (ctx.INTEGER_LITERAL() != null) {
                return ProtoInteger.parse(ctx.INTEGER_LITERAL().getText(), ctx.MINUS() != null);
            } else if (ctx.FLOAT_LITERAL() != null) {
                return ProtoFloat.parse(ctx.FLOAT_LITERAL().getText(), ctx.MINUS() != null);
            } else if (ctx.STRING_LITERAL() != null) {
                return ProtoString.parse(ProtoUtils.removeQuotes(ctx.STRING_LITERAL().getText()));
            } else if (ctx.fullIdentifier() != null) {
                return ProtoConstantIdentifier.parse(ctx.fullIdentifier().getText());
            } else {
                throw new IllegalArgumentException("Invalid constant value " + ctx.getText());
            }
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public ProtoOption visitOption(ProtoParser.OptionContext ctx) {
        try {
            processComments(tokenStream, ctx, true);
            List nestedElements = convertResultToList(super.visitOption(ctx));
            ProtoOptionName optionName = this.findObject(nestedElements, ProtoOptionName.class);
            ProtoConstant constant = this.findObject(nestedElements, ProtoConstant.class);
            ProtoOption option = ProtoOptionImpl.builder()
                    .position(position(ctx))
                    .name(optionName)
                    .value(constant)
                    .build();
            return option;
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitOptionName(ProtoParser.OptionNameContext ctx) {
        try {
            ProtoOptionNameImpl.Builder builder = ProtoOptionNameImpl.builder();
            if (ctx.fullIdentifier() != null) {
                builder.addIdentifier(ctx.fullIdentifier().getText());
                builder.enclosed(true);
            } else {
                builder.addIdentifier(ctx.IDENTIFIER(0).getText());
            }
            for (int k = 1; k < ctx.IDENTIFIER().size(); k++) {
                builder.addIdentifier(ctx.IDENTIFIER(k).getText());
            }
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitMessage(ProtoParser.MessageContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitMessage(ctx));
            ProtoMessage message = ProtoMessageImpl.builder()
                    .position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.messageName())).name(ctx.messageName().IDENTIFIER().getText()).build())
                    .addAllNestedElements(nestedElements)
                    .build();
            return message;
        } catch (ProtoSchemaException ex) {
            notifyErrorListeners(ex.getElement().getPosition(), ex.getMessage());
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitField(ProtoParser.FieldContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitField(ctx));
            ProtoMessageFieldImpl.Builder builder = ProtoMessageFieldImpl.builder();
            builder.position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.fieldName())).name(ctx.fieldName().IDENTIFIER().getText()).build())
                    .fieldNumber(Integer.parseInt(ctx.fieldNumber().INTEGER_LITERAL().getText()))
                    .repeated(ctx.REPEATED_LITERAL() != null)
                    .addAllNestedElements(nestedElements);
            if (ctx.type().protoType() != null) {
                builder.fieldType(ProtoType.basicType(ProtoBasicType.fromString(ctx.type().protoType().getText())));
            } else if (ctx.type().messageOrEnumType() != null) {
                builder.fieldType(ProtoType.messageOrEnumType(ctx.type().getText()));
            } else {
                notifyErrorListeners(position(ctx), "Unknown type " + ctx.type().getText());
            }
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitEnumPB(ProtoParser.EnumPBContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitEnumPB(ctx));
            ProtoEnum enumPB = ProtoEnumImpl.builder()
                    .position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.enumName())).name(ctx.enumName().IDENTIFIER().getText()).build())
                    .addAllNestedElements(nestedElements)
                    .build();
            return enumPB;
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitEnumField(ProtoParser.EnumFieldContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitEnumField(ctx));
            ProtoEnumFieldImpl.Builder builder = ProtoEnumFieldImpl.builder();
            builder.position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.name)).name(ctx.name.getText()).build())
                    .value(Integer.parseInt(ctx.value.getText()))
                    .addAllNestedElements(nestedElements);
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitService(ProtoParser.ServiceContext ctx) {
        Object obj = super.visitService(ctx);
        ProtoService service = ProtoServiceImpl.builder()
                .position(position(ctx))
                .name(ProtoIdentifierImpl.builder().position(position(ctx.serviceName())).name(ctx.serviceName().IDENTIFIER().getText()).build())
                .addAllNestedElements(convertResultToList(obj))
                .build();
        return service;
    }

    @Override
    public Object visitRpc(ProtoParser.RpcContext ctx) {
        ProtoRpc rpc = ProtoRpcImpl.builder()
                .position(position(ctx))
                .name(ProtoIdentifierImpl.builder().position(position(ctx.rpcName())).name(ctx.rpcName().IDENTIFIER().getText()).build())
                .messageType(ProtoType.messageType(ctx.argType.getText()))
                .messageTypeStream(ctx.argStream != null)
                .returnType(ProtoType.messageType(ctx.retType.getText()))
                .returnTypeStream(ctx.retStream != null)
                .build();
        return rpc;
    }

    @Override
    public Object visitSyntax(ProtoParser.SyntaxContext ctx) {
        processComments(tokenStream, ctx, true);
        try {
            Version version = Version.VERSION_3.getName().equals(ProtoUtils.removeQuotes(ctx.version.getText())) ? Version.VERSION_3 : null;
            return ProtoVersionImpl.builder().version(version).build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return ProtoVersionImpl.builder().version(Version.VERSION_3).build();
    }

    @Override
    public Object visitFieldOption(ProtoParser.FieldOptionContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitFieldOption(ctx));
            ProtoOptionName optionName = this.findObject(nestedElements, ProtoOptionName.class);
            ProtoConstant constant = this.findObject(nestedElements, ProtoConstant.class);
            ProtoOptionImpl.Builder builder = ProtoOptionImpl.builder();
            builder.position(position(ctx))
                    .name(optionName)
                    .value(constant);
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitOneof(ProtoParser.OneofContext ctx) {
        List nestedElements = convertResultToList(super.visitOneof(ctx));
        ProtoOneOf message = ProtoOneOfImpl.builder()
                .position(position(ctx))
                .name(ProtoIdentifierImpl.builder().position(position(ctx.oneofName())).name(ctx.oneofName().IDENTIFIER().getText()).build())
                .addAllNestedElements(nestedElements)
                .build();
        return message;
    }

    @Override
    public Object visitOneofField(ProtoParser.OneofFieldContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitOneofField(ctx));
            ProtoOneOfFieldImpl.Builder builder = ProtoOneOfFieldImpl.builder();
            builder.position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.fieldName())).name(ctx.fieldName().IDENTIFIER().getText()).build())
                    .fieldNumber(Integer.parseInt(ctx.fieldNumber().INTEGER_LITERAL().getText()))
                    .addAllNestedElements(nestedElements);
            if (ctx.type().protoType() != null) {
                builder.fieldType(ProtoType.basicType(ProtoBasicType.fromString(ctx.type().protoType().getText())));
            } else if (ctx.type().messageOrEnumType() != null) {
                builder.fieldType(ProtoType.messageOrEnumType(ctx.type().getText()));
            } else {
                notifyErrorListeners(position(ctx), "Unknown type " + ctx.type().getText());
            }
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitMapField(ProtoParser.MapFieldContext ctx) {
        try {
            ProtoMapImpl.Builder builder = ProtoMapImpl.builder();
            builder.position(position(ctx))
                    .name(ProtoIdentifierImpl.builder().position(position(ctx.mapName())).name(ctx.mapName().IDENTIFIER().getText()).build())
                    .fieldNumber(Integer.parseInt(ctx.fieldNumber().INTEGER_LITERAL().getText()))
                    .keyType(ProtoType.basicType(ProtoBasicType.fromString(ctx.mapKeyType().getText())))
                    .valueType(ProtoType.basicType(ProtoBasicType.fromString(ctx.mapKeyType().getText())));
            return builder.build();
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitReserved(ProtoParser.ReservedContext ctx) {
        try {
            List nestedElements = convertResultToList(super.visitReserved(ctx));
            if (nestedElements.isEmpty() == false) {
                ProtoReservedImpl.Builder builder = ProtoReservedImpl.builder();
                builder.position(position(ctx));
                if (ctx.fieldNames() != null) {
                    for (Object obj : nestedElements) {
                        builder.addName((String) obj);
                    }
                } else {
                    for (Object obj : nestedElements) {
                        builder.addRange((ProtoRange) obj);
                    }
                }
                return builder.build();
            }
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    public Object visitReservedFieldName(ProtoParser.ReservedFieldNameContext ctx) {
        String identifier = ProtoUtils.removeQuotes(ctx.getText());
        if (ProtoIdentifier.isValidIdentifier(identifier) == false) {
            throw new IllegalArgumentException("Invalid identifier " + identifier);
        }
        return ProtoUtils.removeQuotes(ctx.getText());
    }

    @Override
    public Object visitRange(ProtoParser.RangeContext ctx) {
        try {
            ProtoInteger min = ProtoInteger.parse(ctx.INTEGER_LITERAL(0).getText());
            ProtoInteger max = null;
            if (ctx.EXTENSIONS_MAX_LITERAL() != null) {
                max = new ProtoInteger(ProtoFieldNumber.MAX_NUMBER, ProtoInteger.Formatter.DECIMAL, "max");
            } else if (ctx.EXTENSIONS_TO_LITERAL() != null) {
                max = ProtoInteger.parse(ctx.INTEGER_LITERAL(1).getText());
            }
            if (ProtoFieldNumber.isValid(min.getValue()) == false) {
                throw new IllegalArgumentException("Invalid range, min is invalid");
            }
            if (max != null && ProtoFieldNumber.isValid(max.getValue()) == false) {
                throw new IllegalArgumentException("Invalid range, max is invalid");
            }
            if (max != null && min.getValue() > max.getValue()) {
                throw new IllegalArgumentException("Invalid range, min > max");
            }
            return max != null ? new ProtoRange((int) (long) min.getValue(), (int) (long) max.getValue()) : new ProtoRange((int) (long) min.getValue());
        } catch (IllegalArgumentException ex) {
            notifyErrorListeners(position(ctx), ex.getMessage());
        }
        return null;
    }

    @Override
    protected Object aggregateResult(Object aggregate, Object nextResult) {
        if (nextResult == null) {
            return aggregate;
        } else if (aggregate == null) {
            return nextResult;
        } else {
            if (aggregate instanceof AggregateArrayList == false) {
                AggregateArrayList tmp = new AggregateArrayList<>();
                tmp.add(aggregate);
                tmp.add(nextResult);
                return tmp;
            } else {
                ((List) aggregate).add(nextResult);
                return aggregate;
            }
        }
    }

    private static  List convertResultToList(Object obj) {
        if (obj == null) {
            return Collections.EMPTY_LIST;
        }
        if (obj instanceof AggregateArrayList) {
            return (List) obj;
        } else {
            List ret = new AggregateArrayList<>(1);
            ret.add((T) obj);
            return ret;
        }

    }

    private static ProtoPosition position(Token token) {
        int line = token.getLine();
        int column = token.getCharPositionInLine();
        int startIndex = token.getStartIndex();
        int endIndex = token.getStopIndex();
        return new ProtoPosition(startIndex, endIndex, line, column, column + token.getText().length());
    }

    private static ProtoPosition position(ParserRuleContext ctx) {
        Interval interval = ctx.getSourceInterval();
        Token firstToken = ctx.getStart();
        int line = firstToken.getLine();
        int column = firstToken.getCharPositionInLine();
        int startIndex = firstToken.getStartIndex();
        Token lastToken = ctx.getStop();
        int endIndex = lastToken.getStopIndex();
        return new ProtoPosition(startIndex, endIndex, line, column, column + interval.length());
    }

    private void notifyErrorListeners(ProtoPosition position, String message) {
        errorListener.addError(position, message);
        if (parser.getParserOptions().getParseCancellationMode() == ProtoParserOptions.ParserCancellationMode.CANCEL_AFTER_FIRST_ERROR) {
            throw new ParseCancellationException(message);
        }
    }

    private  T findObject(List objects, Class clazz) {
        if (objects == null) {
            return null;
        }
        Optional ret = objects.stream().filter(o -> clazz.isInstance(o)).map(o -> (T) o).findFirst();
        return ret.isPresent() == true ? ret.get() : null;
    }

    private  List findObjects(List objects, Class clazz) {
        if (objects == null) {
            return Collections.EMPTY_LIST;
        }
        return objects.stream().filter(o -> clazz.isInstance(o)).map(o -> (T) o).collect(Collectors.toList());
    }

}