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

org.eclipse.steady.python.Python335FileAnalyzer Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/**
 * This file is part of Eclipse Steady.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.python;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;

import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.apache.logging.log4j.Logger;
import org.eclipse.steady.Construct;
import org.eclipse.steady.ConstructId;
import org.eclipse.steady.FileAnalysisException;
import org.eclipse.steady.FileAnalyzer;
import org.eclipse.steady.FileAnalyzerFactory;
import org.eclipse.steady.python.antlr.python335.Python335BaseListener;
import org.eclipse.steady.python.antlr.python335.Python335Lexer;
import org.eclipse.steady.python.antlr.python335.Python335Parser;
import org.eclipse.steady.shared.util.FileUtil;
import org.eclipse.steady.shared.util.StringUtil;

// TODO: Decide what to do with default arg values in functions and methods? Right now, they are
// part of the qname, which is probably wrong.
/**
 * 

Python335FileAnalyzer class.

*/ public class Python335FileAnalyzer extends Python335BaseListener implements FileAnalyzer { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); protected Map constructs = null; /** Nested definitions. */ protected final Stack context = new Stack(); /** Scripting statements, i.e., all statements outside of functions and classes. */ protected final List stmts = new ArrayList(); /** Number of times a given function has been defined in the given context. */ protected final Map> countPerContext = new HashMap>(); private PythonId module = null; private File file = null; /** * {@inheritDoc} * * Will not be instantiated by the {@link FileAnalyzerFactory}, but by {@link PythonFileAnalyzer}. */ @Override public String[] getSupportedFileExtensions() { return new String[] {}; } /** *

Constructor for Python335FileAnalyzer.

*/ public Python335FileAnalyzer() { super(); } /** * Sets context information in case an {@link InputStream} is parsed using {@link Python3FileAnalyzer#getConstructs(InputStream)}. * In this case, package and module information cannot be obtained from the file and file system. * The method is called by {@link PythonArchiveAnalyzer}. * * @param _module a {@link org.eclipse.steady.python.PythonId} object. * @param _pack a {@link org.eclipse.steady.python.PythonId} object. */ public void setContext(PythonId _module, PythonId _pack) { this.constructs = new TreeMap(); if (_pack != null) { this.context.push(_pack); this.constructs.put(_pack, new Construct(_pack, "")); } this.context.push(_module); this.module = _module; this.constructs.put(module, new Construct(module, "")); } /** {@inheritDoc} */ @Override public boolean canAnalyze(File _file) { final String ext = FileUtil.getFileExtension(_file); if (ext == null || ext.equals("")) return false; for (String supported_ext : this.getSupportedFileExtensions()) { if (supported_ext.equalsIgnoreCase(ext)) return true; } return false; } /** {@inheritDoc} */ @Override public void analyze(final File _file) throws FileAnalysisException { if (!FileUtil.isAccessibleFile(_file.toPath())) throw new IllegalArgumentException("[" + _file + "] does not exist or is not readable"); this.file = _file; } /** * {@inheritDoc} * * Enter a parse tree produced by {@link Python335Parser#stmt}. */ @Override public void enterStmt( Python335Parser.StmtContext ctx) { // TODO reads stmts before class defs and func defs are entered final String stmt = ctx.getText(); if (!PythonFileAnalyzer.isTopOfType(this.context, PythonId.Type.MODULE) || stmt.startsWith("def") || stmt.startsWith("class") // || stmt.startsWith("import") // || stmt.startsWith("from") ) return; stmts.add(stmt); } /** {@inheritDoc} */ @Override public void enterClassdef(Python335Parser.ClassdefContext ctx) { // Happens if class name is 'async', due to the Python grammar's problem with the ASYNC keyword, // cf. testPythonFileWithAsync if (ctx.NAME() == null) throw new IllegalStateException( "Parser error: Class without name in context " + this.context + ", line [" + ctx.getStart().getLine() + "]"); final String name = ctx.NAME().toString(); String parent_classes = ""; if (ctx.arglist() != null) parent_classes = ctx.arglist().getText().toString(); // Create construct and add to context final PythonId id = new PythonId(this.context.peek(), PythonId.Type.CLASS, name + "(" + parent_classes + ")"); final Construct c = new Construct(id, ctx.getText()); this.constructs.put(id, c); this.context.push(id); } /** {@inheritDoc} */ @Override public void exitClassdef(Python335Parser.ClassdefContext ctx) { if (!PythonFileAnalyzer.isTopOfType(this.context, PythonId.Type.CLASS)) log.error( "Top most element in stack is not of type [" + PythonId.Type.CLASS + "], line [" + ctx.getStart().getLine() + "]"); else context.pop(); } /** {@inheritDoc} */ @Override public void enterFuncdef(Python335Parser.FuncdefContext ctx) { // Happens if method or function name is 'async', due to the Python grammar's problem with the // ASYNC keyword, cf. testPythonFileWithAsync if (ctx.NAME() == null) throw new IllegalStateException( "Parser error: Construct without name in context " + this.context + ", line [" + ctx.getStart().getLine() + "]"); PythonId id; String name = ctx.NAME().toString(); String args = ctx.parameters().getText(); // Identical construct names can be used within a single context (e.g., the same function in a // module) name = this.getNameForCurrentContext(name); // New type depends on context type final PythonId.Type ctx_type = this.context.peek().getType(); if (ctx_type == PythonId.Type.CLASS && name.equals("__init__")) id = new PythonId(this.context.peek(), PythonId.Type.CONSTRUCTOR, name + args); else if (ctx_type == PythonId.Type.CLASS) id = new PythonId(this.context.peek(), PythonId.Type.METHOD, name + args); else if (ctx_type == PythonId.Type.MODULE || ctx_type == PythonId.Type.FUNCTION || ctx_type == PythonId.Type.METHOD || ctx_type == PythonId.Type.CONSTRUCTOR) id = new PythonId(this.context.peek(), PythonId.Type.FUNCTION, name + args); else { log.error( "Cannot create method, constructor or class due to wrong context: " + this.context + ", line [" + ctx.getStart().getLine() + "]"); throw new IllegalStateException( "Error when parsing [" + this.file + "]: Cannot create method or class due to wrong context: " + this.context); } final Construct c = new Construct(id, ctx.getText()); this.constructs.put(id, c); this.context.push(id); } /** {@inheritDoc} */ @Override public void exitFuncdef(Python335Parser.FuncdefContext _ctx) { final PythonId.Type[] types = new PythonId.Type[] { PythonId.Type.FUNCTION, PythonId.Type.CONSTRUCTOR, PythonId.Type.METHOD }; if (!PythonFileAnalyzer.isTopOfType(this.context, types)) log.error( "Top most element in stack is not of the following types [" + StringUtil.join((Object[]) types, ", ") + "]" + ", line [" + _ctx.getStart().getLine() + "]"); else context.pop(); } /** * * @param _name * @return */ private String getNameForCurrentContext(String _name) { final PythonId ctx = this.context.peek(); if (this.countPerContext.get(ctx) == null) this.countPerContext.put(ctx, new HashMap()); if (this.countPerContext.get(ctx).get(_name) == null) this.countPerContext.get(ctx).put(_name, Integer.valueOf(0)); int count = this.countPerContext.get(ctx).get(_name).intValue(); this.countPerContext.get(ctx).put(_name, Integer.valueOf(++count)); if (count == 1) return _name; else return _name + "$" + count; } /** {@inheritDoc} */ @Override public boolean containsConstruct(ConstructId _id) throws FileAnalysisException { return this.constructs.containsKey(_id); } /** * Maybe promote this method, which uses the shared type as argument, to the interface. * Alternatively, make all core-internal interfaces work with core types, not with shared * types. Example to be changed: SignatureFactory. * * @param _id a {@link org.eclipse.steady.shared.json.model.ConstructId} object. * @return a {@link org.eclipse.steady.Construct} object. * @throws org.eclipse.steady.FileAnalysisException if any. */ public Construct getConstruct(org.eclipse.steady.shared.json.model.ConstructId _id) throws FileAnalysisException { for (Map.Entry e : this.constructs.entrySet()) if (ConstructId.toSharedType(e.getKey()).equals(_id)) return e.getValue(); return null; } /** {@inheritDoc} */ @Override public Construct getConstruct(ConstructId _id) throws FileAnalysisException { return this.constructs.get(_id); } /** *

Getter for the field constructs.

* * @param m a {@link java.io.InputStream} object. * @return a {@link java.util.Map} object. * @throws org.eclipse.steady.FileAnalysisException if any. * @throws java.io.IOException if any. * @throws org.antlr.v4.runtime.RecognitionException if any. */ public Map getConstructs(InputStream m) throws FileAnalysisException, IOException, RecognitionException { final CharStream input = CharStreams.fromStream(m); final Python335Lexer lexer = new Python335Lexer(input); final CommonTokenStream tokens = new CommonTokenStream(lexer); final Python335Parser parser = new Python335Parser(tokens); final ParseTree root = parser.file_input(); final ParseTreeWalker walker = new ParseTreeWalker(); try { walker.walk(this, root); } catch (IllegalStateException ise) { throw new FileAnalysisException("Parser error", ise); } // Update module body after the parsing of the entire file if (this.stmts != null && this.stmts.size() > 0) { final StringBuffer b = new StringBuffer(); for (String stmt : this.stmts) if (!stmt.trim().equals("")) b.append(stmt); this.constructs.get(this.module).setContent(b.toString()); } return this.constructs; } /** {@inheritDoc} */ @Override public Map getConstructs() throws FileAnalysisException { if (this.constructs == null) { try { this.constructs = new TreeMap(); // Create module and add to constructs this.module = PythonFileAnalyzer.getModule(this.file); this.constructs.put(this.module, new Construct(this.module, "")); // Get package, if any, and add to constructs final PythonId pack = this.module.getPackage(); if (pack != null) this.constructs.put(pack, new Construct(pack, "")); // Use package and module as context if (pack != null) this.context.push(pack); this.context.push(module); // Parse the file log.debug("Parsing [" + this.file + "]"); try (FileInputStream fis = new FileInputStream(this.file)) { this.getConstructs(fis); } } catch (FileNotFoundException e) { throw new FileAnalysisException(e.getMessage(), e); } catch (RecognitionException e) { throw new FileAnalysisException( "ANTLR exception while analysing class file [" + this.file.getName() + "]: " + e.getMessage(), e); } catch (IOException e) { throw new FileAnalysisException( "IO exception while analysing class file [" + this.file.getName() + "]: " + e.getMessage(), e); } } return this.constructs; } /** {@inheritDoc} */ @Override public boolean hasChilds() { return false; } /** {@inheritDoc} */ @Override public Set getChilds(boolean _recursive) { return null; } /** {@inheritDoc} */ @Override public String toString() { return "Python335 parser for [" + this.file + "]"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy