com.sap.psr.vulas.java.JavaFileAnalyzer2 Maven / Gradle / Ivy
/**
* 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
*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.psr.vulas.java;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sap.psr.vulas.Construct;
import com.sap.psr.vulas.ConstructId;
import com.sap.psr.vulas.FileAnalysisException;
import com.sap.psr.vulas.FileAnalyzer;
import com.sap.psr.vulas.java.antlr.JavaLexer;
import com.sap.psr.vulas.java.antlr.JavaParser;
import com.sap.psr.vulas.java.antlr.JavaParser.CompilationUnitContext;
import com.sap.psr.vulas.java.antlr.JavaParser.FormalParameterContext;
import com.sap.psr.vulas.java.antlr.JavaParser.TypeTypeContext;
import com.sap.psr.vulas.java.antlr.JavaParserBaseListener;
import com.sap.psr.vulas.shared.util.FileUtil;
/**
* Analyzes java source files using ANTLR.
*/
public class JavaFileAnalyzer2 extends JavaParserBaseListener implements FileAnalyzer {
private static final Log log = LogFactory.getLog(JavaFileAnalyzer2.class);
/**
* All Java constructs found in the given Java file, created through visiting relevant nodes of the ANTLR parse tree.
* The values are return by {@link #getConstructs()}. Java enums and interfaces are ignored, as they rarely contain
* executable code.
*/
private Map constructs = null;
private ANTLRInputStream input = null;
/** The file to be analyzed. */
private File file = null;
/**
* Package, class, enum and interface declarations found while parsing a Java source file.
* The topmost element will be used as declaration context of methods and constructors.
*/
private final ContextStack contextStack = new ContextStack();
/** Used for the construction of nested named and anonynous classes. */
private final ConstructIdBuilder constructIdBuilder = new ConstructIdBuilder();
/** {@inheritDoc} */
@Override
public String[] getSupportedFileExtensions() {
return new String[] { "java" };
}
/** {@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 {
this.setFile(_file);
}
/**
* Setter for the field file
.
*
* @param _file a {@link java.io.File} object.
* @throws java.lang.IllegalArgumentException if any.
*/
public void setFile(File _file) throws IllegalArgumentException {
final String ext = FileUtil.getFileExtension(_file);
if(!ext.equals("java"))
throw new IllegalArgumentException("Expected a java file but got [" + _file + "]");
if(!FileUtil.isAccessibleFile(_file.toPath()))
throw new IllegalArgumentException("Cannot open file [" + _file + "]");
this.file = _file;
}
/**
* Creates and adds a new {@link Construct} to the set of constructs found in the analyzed file.
* This method is called during the various visitor methods inherited from {@link JavaBaseListener}.
* @param _id
* @param _body
*/
private void saveConstruct(ConstructId _id, String _body) {
try {
final Construct c = new Construct(_id, _body);
this.constructs.put(_id, c);
JavaFileAnalyzer2.log.debug("Added " + c.getId());
} catch (IllegalArgumentException e) {
JavaFileAnalyzer2.log.error(e);
}
}
/** {@inheritDoc} */
@Override
public void enterPackageDeclaration(@NotNull JavaParser.PackageDeclarationContext ctx) {
// Create JavaId
final JavaPackageId id = new JavaPackageId(ctx.getChild(1).getText());
// Add to the stack
this.contextStack.push(id);
// Create the construct
this.saveConstruct(id, ctx.getParent().getText());
}
/**
* {@inheritDoc}
*
* Enums are added to {@link #constructs}.
*/
@Override
public void enterEnumDeclaration(@NotNull JavaParser.EnumDeclarationContext ctx) {
// Create JavaId and push to the stack
final ContextStackEntry cse = this.contextStack.peek();
final JavaId decl_ctx = ( cse==null ? JavaPackageId.DEFAULT_PACKAGE : (JavaId)cse.getConstructId() );
final JavaId id = new JavaEnumId(decl_ctx, ctx.IDENTIFIER().getText());
this.contextStack.push(id);
this.saveConstruct(id, this.getConstructContent(ctx));
}
/** {@inheritDoc} */
@Override
public void exitEnumDeclaration(@NotNull JavaParser.EnumDeclarationContext ctx) {
final JavaId id = (JavaId)this.contextStack.pop().getConstructId();
this.isOfExpectedType(id, new JavaId.Type[] { JavaId.Type.ENUM }, true);
}
/**
* {@inheritDoc}
*
* Interfaces are not added to {@link #constructs}.
*/
@Override
public void enterInterfaceDeclaration(@NotNull JavaParser.InterfaceDeclarationContext ctx) {
// Create JavaId and push to the stack
final ContextStackEntry cse = this.contextStack.peek();
final JavaId decl_ctx = ( cse==null ? JavaPackageId.DEFAULT_PACKAGE : (JavaId)cse.getConstructId() );
final JavaId id = new JavaInterfaceId(decl_ctx, ctx.IDENTIFIER().getText());
this.contextStack.push(id);
}
/** {@inheritDoc} */
@Override
public void exitInterfaceDeclaration(@NotNull JavaParser.InterfaceDeclarationContext ctx) {
final JavaId id = (JavaId)this.contextStack.pop().getConstructId();
this.isOfExpectedType(id, new JavaId.Type[] { JavaId.Type.INTERFACE }, true);
}
/** {@inheritDoc} */
@Override
public void enterClassDeclaration(@NotNull JavaParser.ClassDeclarationContext ctx) {
// Remember the declaration context (needed for the handling of anon. classes in enterClassBody)
this.constructIdBuilder.setCurrentDeclarationContext(ctx.IDENTIFIER().getText());
}
/** {@inheritDoc} */
@Override
public void exitClassDeclaration(@NotNull JavaParser.ClassDeclarationContext ctx) {}
/** {@inheritDoc} */
@Override
public void enterClassBody(@NotNull JavaParser.ClassBodyContext ctx) {
// Create JavaId and push to the stack
final JavaId id = (JavaClassId) this.constructIdBuilder.buildJavaClassId();
this.contextStack.push(id);
this.saveConstruct(id, this.getConstructContent(ctx));
// Log anon classes
if(this.constructIdBuilder.isAnonymousClass())
JavaFileAnalyzer2.log.debug(this.indent(this.contextStack.size()) + "Enter anon class body " + id.toString() + " " + this.printDeclarationStack());
this.constructIdBuilder.resetCurrentDeclarationContext();
}
/** {@inheritDoc} */
@Override
public void exitClassBody(@NotNull JavaParser.ClassBodyContext ctx) {
final JavaId id = (JavaId) this.contextStack.pop().getConstructId();
this.isOfExpectedType(id, new JavaId.Type[] { JavaId.Type.CLASS }, true);
this.constructIdBuilder.resetCurrentDeclarationContext();
}
/** {@inheritDoc} */
@Override
public void enterMethodDeclaration(@NotNull JavaParser.MethodDeclarationContext ctx) {
// Peek JavaId and ensure it is a class or enum
final JavaId class_ctx = (JavaId) this.contextStack.peek().getConstructId();
this.isOfExpectedType(class_ctx, new JavaId.Type[] { JavaId.Type.CLASS, JavaId.Type.ENUM }, true);
// Build the identifier
final JavaMethodId id = new JavaMethodId((JavaId) class_ctx, ctx.IDENTIFIER().getText(),
this.getParameters(ctx.formalParameters().formalParameterList()));
this.contextStack.push(id);
this.saveConstruct(id, this.getConstructContent(ctx));
}
/** {@inheritDoc} */
@Override
public void exitMethodDeclaration(com.sap.psr.vulas.java.antlr.JavaParser.MethodDeclarationContext ctx) {
final JavaId id = (JavaId) this.contextStack.pop().getConstructId();
}
/** {@inheritDoc} */
@Override
public void enterConstructorDeclaration(@NotNull JavaParser.ConstructorDeclarationContext ctx) {
// Peek JavaId and ensure it is a class or enum
final JavaId class_ctx = (JavaId) this.contextStack.peek().getConstructId();
this.isOfExpectedType(class_ctx, new JavaId.Type[] { JavaId.Type.CLASS, JavaId.Type.ENUM }, true);
// Build the identifier
final JavaId id = new JavaConstructorId((JavaId)class_ctx,
this.getParameters(ctx.formalParameters().formalParameterList()));
this.contextStack.push(id);
this.saveConstruct(id, this.getConstructContent(ctx));
}
/** {@inheritDoc} */
@Override
public void exitConstructorDeclaration(com.sap.psr.vulas.java.antlr.JavaParser.ConstructorDeclarationContext ctx) {
final JavaId id = (JavaId)this.contextStack.pop().getConstructId();
}
/**
* Retrieves content for constructs of type Method, Constructor and Class.
* @param ctx - ParseRuleContex
* @return Extracted construct Body
*/
private final String getConstructContent(ParserRuleContext ctx){
final int a = ctx.start.getStartIndex();
final int b = ctx.stop.getStopIndex();
final Interval interval = new Interval(a,b);
final String text = this.input.getText(interval);
return text;
}
private boolean isOfExpectedType(JavaId _jid, JavaId.Type[] _types, boolean _throw_exception) {
boolean is = true;
if(_jid==null || !Arrays.asList(_types).contains(_jid.getType())) {
is = false;
if(_throw_exception) {
JavaFileAnalyzer2.log.error("Expected [" + _types[0] + "], got " + _jid);
throw new IllegalStateException("Expected [" + _types[0] + "], got " + _jid);
} else {
JavaFileAnalyzer2.log.warn("Expected [" + _types[0] + "], got " + _jid);
}
is = false;
}
return is;
}
/**
* Returns true if the construct stack only consists of classes. It allows skipping
* all declarations happening in enums and interfaces, nested or not.
*/
/*private boolean isClassDeclarationsOnly(ParserRuleContext _context) {
boolean is = true;
for(ContextStackEntry jid : this.contextStack.all()) {
if ( !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.PACKAGE)
&& !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.CLASS)
&& !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.METHOD) ) {
// Get the name of the current declaration
String item = null;
if (_context instanceof JavaParser.ClassDeclarationContext)
item = "class [" + ((JavaParser.ClassDeclarationContext) _context).Identifier().getText() + "]";
// else if (_context instanceof
// JavaParser.MethodDeclarationContext)
// item = "method [" + ((JavaParser.MethodDeclarationContext)
// _context).Identifier().getText() + "]";
else if (_context instanceof JavaParser.ConstructorDeclarationContext)
item = "constructor ["
+ ((JavaParser.ConstructorDeclarationContext) _context).Identifier().getText() + "]";
else if (_context instanceof JavaParser.ClassBodyContext)
item = "classBody";
// if(_context instanceof JavaParser.ClassDeclarationContext)
// item = "class [" + ((JavaParser.ClassDeclarationContext)_context).Identifier().getText() + "]";
// else if(_context instanceof JavaParser.MethodDeclarationContext)
// item = "method [" + ((JavaParser.MethodDeclarationContext)_context).Identifier().getText() + "]";
// else if(_context instanceof JavaParser.ConstructorDeclarationContext)
// item = "constructor [" + ((JavaParser.ConstructorDeclarationContext)_context).Identifier().getText() + "]";
JavaFileAnalyzer2.log.info("Declaration of " + item + " will be skipped, it is inside a nested declarations including enums and/or interfaces");
is = false;
break;
}
}
return is;
}*/
/*private boolean isClassDeclarationsOnly(ParserRuleContext _context) {
boolean is = true;
for(ContextStackEntry jid : this.contextStack.all()) {
if ( !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.PACKAGE)
&& !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.CLASS)
&& !((JavaId)jid.getConstructId()).getType().equals(JavaId.Type.METHOD) ) {
// Get the name of the current declaration
String item = null;
if (_context instanceof JavaParser.ClassDeclarationContext)
item = "class [" + ((JavaParser.ClassDeclarationContext) _context).Identifier().getText() + "]";
// else if (_context instanceof
// JavaParser.MethodDeclarationContext)
// item = "method [" + ((JavaParser.MethodDeclarationContext)
// _context).Identifier().getText() + "]";
else if (_context instanceof JavaParser.ConstructorDeclarationContext)
item = "constructor ["
+ ((JavaParser.ConstructorDeclarationContext) _context).Identifier().getText() + "]";
else if (_context instanceof JavaParser.ClassBodyContext)
item = "classBody";
// if(_context instanceof JavaParser.ClassDeclarationContext)
// item = "class [" + ((JavaParser.ClassDeclarationContext)_context).Identifier().getText() + "]";
// else if(_context instanceof JavaParser.MethodDeclarationContext)
// item = "method [" + ((JavaParser.MethodDeclarationContext)_context).Identifier().getText() + "]";
// else if(_context instanceof JavaParser.ConstructorDeclarationContext)
// item = "constructor [" + ((JavaParser.ConstructorDeclarationContext)_context).Identifier().getText() + "]";
JavaFileAnalyzer2.log.info("Declaration of " + item + " will be skipped, it is inside a nested declarations including enums and/or interfaces");
is = false;
break;
}
}
return is;
}*/
private List getParameters(JavaParser.FormalParameterListContext _ctx) {
if(_ctx==null) return null;
else {
List l = new ArrayList();
List list = _ctx.formalParameter();
for(FormalParameterContext par_ctx: list) {
TypeTypeContext type_ctx = par_ctx.typeType();
String t = type_ctx.getText();
// Simply remove the parameterization of generic classes
// This is possible, as they do not matter in terms of method overloading
// Example: to methods foo(Set) and foo(Set
© 2015 - 2024 Weber Informatics LLC | Privacy Policy