com.googlecode.goclipse.tooling.oracle.GuruPackageDescribeParser.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.goclipse.tooling.oracle.GuruPackageDescribeParser.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bruno Medeiros - initial API and implementation
 *******************************************************************************/
package com.googlecode.goclipse.tooling.oracle;

import static melnorme.lang.tooling.structure.StructureElementKind.STRUCT;
import static melnorme.utilbox.core.CoreUtil.list;

import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import melnorme.lang.tooling.EProtection;
import melnorme.lang.tooling.ElementAttributes;
import melnorme.lang.tooling.ast.ParserErrorTypes;
import melnorme.lang.tooling.ast.SourceRange;
import melnorme.lang.tooling.common.LineColumnPosition;
import melnorme.lang.tooling.common.ParserError;
import melnorme.lang.tooling.common.SourceFileLocation;
import melnorme.lang.tooling.structure.AbstractStructureParser;
import melnorme.lang.tooling.structure.SourceFileStructure;
import melnorme.lang.tooling.structure.StructureElement;
import melnorme.lang.tooling.structure.StructureElementKind;
import melnorme.lang.tooling.toolchain.ops.ToolOutputParseHelper;
import melnorme.lang.utils.gson.GsonHelper;
import melnorme.lang.utils.gson.JsonParserX;
import melnorme.lang.utils.parse.LexingUtils;
import melnorme.lang.utils.parse.StringCharSource;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.Location;
import melnorme.utilbox.misc.StringUtil;
import melnorme.utilbox.process.ExternalProcessHelper.ExternalProcessResult;

public class GuruPackageDescribeParser extends AbstractStructureParser {

    protected final GsonHelper helper = new GsonHelper();

    public GuruPackageDescribeParser(Location location, String goSource) {
        super(location, goSource);
    }

    public SourceFileStructure parse(ExternalProcessResult result) throws CommonException {
        if (result.exitValue != 0) {
            String errorMsg = result.getStdErrBytes().toString(StringUtil.UTF8);
            return parseErrorMessage(errorMsg);
        }

        return parse(result.getStdOutBytes().toString(StringUtil.UTF8));
    }

    @Override
    public SourceFileStructure parse(String describeOutput) throws CommonException {
        ArrayList2<StructureElement> elements = doParseJsonResult(describeOutput);

        return new SourceFileStructure(location, elements, null);
    }

    protected ArrayList2<StructureElement> doParseJsonResult(String output) throws CommonException {
        JsonObject describe = new JsonParserX().parseObject(output, true);

        JsonObject packageObj = helper.getObject(describe, "package");

        JsonArray members = helper.getOptionalArray(packageObj, "members");
        return parseElements(members, false);
    }

    protected ArrayList2<StructureElement> parseElements(JsonArray members, boolean parsingMethods)
            throws CommonException {
        ArrayList2<StructureElement> elements = new ArrayList2<>();

        if (members != null) {
            for (int i = 0; i < members.size(); i++) {
                JsonElement arrayElement = members.get(i);
                if (arrayElement.isJsonObject()) {
                    JsonObject jsonObject = arrayElement.getAsJsonObject();
                    StructureElement structureElement = parseStructureElement(jsonObject, parsingMethods);
                    if (structureElement == null) {
                        continue; // Can happen for external elements
                    }
                    elements.add(structureElement);
                } else {
                    throw new CommonException("'members' array element is not a JSONObject: " + arrayElement);
                }
            }
        }

        Collections.sort(elements, new Comparator<StructureElement>() {
            @Override
            public int compare(StructureElement o1, StructureElement o2) {
                SourceRange sr1 = o1.getSourceRange();
                SourceRange sr2 = o2.getSourceRange();

                int cmp = sr1.getOffset() - sr2.getOffset();
                if (cmp == 0) {
                    int offset1 = o1.getNameSourceRange2() == null ? 0 : o1.getNameSourceRange2().getOffset();
                    int offset2 = o2.getNameSourceRange2() == null ? 0 : o2.getNameSourceRange2().getOffset();
                    return offset1 - offset2;
                }
                return cmp;
            }
        });

        return elements;
    }

    protected StructureElement parseStructureElement(JsonObject object, boolean parsingMethods)
            throws CommonException {
        String name = helper.getString(object, "name");

        String posString = helper.getString(object, "pos");
        SourceFileLocation elementSourceFileLoc = SourceFileLocation.parseSourceRange(posString, ':');

        SourceRange nameSourceRange;
        SourceRange sourceRange;

        if (!isSourceElementLocation(elementSourceFileLoc.getFileLocation())) {
            sourceRange = nameSourceRange = null;
        } else {
            sourceRange = nameSourceRange = elementSourceFileLoc.parseSourceRangeFrom1BasedIndex(sourceLinesInfo);
        }

        String type = helper.getOptionalString(object, "type");

        String kindString = helper.getOptionalString(object, "kind");

        StructureElementKind elementKind;
        if (parsingMethods) {
            elementKind = StructureElementKind.METHOD;
        } else {
            if (kindString == null) {
                throw new CommonException("No `kind` field for element: " + name);
            }
            elementKind = parseKind(kindString, type);
        }
        if (elementKind == STRUCT || elementKind == StructureElementKind.INTERFACE) {
            type = null;
        } else if (elementKind == StructureElementKind.METHOD) {
            if (name.startsWith("method ")) {
                name = name.substring("method ".length());
                if (name.startsWith("(")) {
                    String fullName = StringUtil.segmentAfterMatch(name, ")");
                    if (fullName != null) {
                        fullName = fullName.trim();
                        int idLength = parseIdentifierStart(fullName);
                        if (idLength > 0) {
                            name = fullName.substring(0, idLength);
                            type = "func" + fullName.substring(idLength);
                        }
                    }
                }
            }
        }

        if (name.length() == 0) {
            throw new CommonException("No name provided");
        }

        EProtection protection = EProtection.PUBLIC;

        if (!parsingMethods && Character.isLowerCase(name.charAt(0))) {
            protection = EProtection.PRIVATE;
        }

        ElementAttributes elementAttributes = new ElementAttributes(protection);

        JsonArray methods = helper.getOptionalArray(object, "methods");
        Indexable<StructureElement> children = parseElements(methods, true);

        if (!isSourceElementLocation(elementSourceFileLoc.getFileLocation())) {
            // Fix source range to children range.
            if (children.size() == 0) {
                return null; // Shouldn't even happen
            }
            nameSourceRange = null;
            int startPos = children.get(0).getSourceRange().getStartPos();
            int endPos = children.get(children.size() - 1).getSourceRange().getEndPos();
            sourceRange = SourceRange.srStartToEnd(startPos, endPos);
        }

        return new StructureElement(name, nameSourceRange, sourceRange, elementKind, elementAttributes, type,
                children);
    }

    protected boolean isSourceElementLocation(Location sourceFileLoc) throws CommonException {
        return location == null || location.equals(sourceFileLoc);
    }

    protected int parseIdentifierStart(String source) {
        StringCharSource parser = new StringCharSource(source);
        return LexingUtils.matchJavaIdentifier(parser);
    }

    protected StructureElementKind parseKind(String kind, String type) {
        if (kind == null) {
            return null;
        }

        switch (kind.toLowerCase()) {
        case "func":
            return StructureElementKind.FUNCTION;
        case "var":
            return StructureElementKind.VARIABLE;
        case "const":
            return StructureElementKind.CONST;
        case "type":

            if (type.startsWith("struct")) {
                return StructureElementKind.STRUCT;
            }
            if (type.startsWith("interface")) {
                return StructureElementKind.INTERFACE;
            }

            return StructureElementKind.TYPE_DECL;
        default:
            return StructureElementKind.VARIABLE;
        }

    }

    protected static final Pattern GO_MESSAGE_LINE_Regex = Pattern.compile("^([^:\\n]*):" + // file
            "(\\d*):((\\d*):)?" + // line:column
            //      "( (\\d*):(\\d*))?" + // end line:column
            //      "()" + // column-end
            "\\s(.*)$" // error message
    );

    public SourceFileStructure parseErrorMessage(String errorMsg) throws CommonException {
        errorMsg = StringUtil.substringUntilLastMatch(errorMsg, "\n");
        errorMsg = StringUtil.trimStart(errorMsg, "oracle: ");
        errorMsg = StringUtil.trimStart(errorMsg, "guru: ");

        ArrayList2<ParserError> parserProblems = new ArrayList2<>();

        if (errorMsg.length() > 2 && errorMsg.charAt(1) == ':') {
            // The path has a Windows driver letter, we need to remove it so it doesn't mess up the regex
            errorMsg = errorMsg.substring(2);
        }
        Matcher matcher = GO_MESSAGE_LINE_Regex.matcher(errorMsg);
        if (!matcher.matches()) {
            throw new CommonException(errorMsg);
        }

        String lineStr = matcher.group(2);
        String columnStr = matcher.group(4);
        String message = matcher.group(5);

        LineColumnPosition lcPost = ToolOutputParseHelper.parseLineColumn(lineStr, columnStr, 1, 1);

        int offset = sourceLinesInfo.getValidatedOffset_1(lcPost.line, lcPost.column);
        String source = sourceLinesInfo.getSource();
        int length = 1;

        if (offset == source.length()) {
            length = 0;
        } else if (offset < source.length()) {
            length = heuristic_determinTokenLength(offset, source);
        }

        SourceRange sr = new SourceRange(offset, length);
        parserProblems.add(new ParserError(ParserErrorTypes.GENERIC_ERROR, sr, message, null));

        return new SourceFileStructure(location, list(), parserProblems);
    }

    /**
     * Try to guess the length of the token at given offset, from given source.
     * Doesn't have to be accurate measurement. (used to get a better range for error msgs)  
     */
    protected int heuristic_determinTokenLength(int offset, String source) {

        StringCharSource charSource = new StringCharSource(source);
        charSource.consumeAmount(offset);
        int length = LexingUtils.matchJavaIdentifier(charSource);
        if (length == 0) {
            return 1;
        }
        return length;
    }

}