diff --git a/cldk/analysis/java/codeanalyzer/codeanalyzer.py b/cldk/analysis/java/codeanalyzer/codeanalyzer.py index 6d806abd..4fdb03dd 100644 --- a/cldk/analysis/java/codeanalyzer/codeanalyzer.py +++ b/cldk/analysis/java/codeanalyzer/codeanalyzer.py @@ -67,7 +67,10 @@ def __init__( self.eager_analysis = eager_analysis self.analysis_level = analysis_level self.target_files = target_files - self.application = self._init_codeanalyzer(analysis_level=1 if analysis_level == AnalysisLevel.symbol_table else 2) + if self.source_code is None: + self.application = self._init_codeanalyzer(analysis_level=1 if analysis_level == AnalysisLevel.symbol_table else 2) + else: + self.application = self._codeanalyzer_single_file() # Attributes related the Java code analysis... if analysis_level == AnalysisLevel.call_graph: self.call_graph: nx.DiGraph = self._generate_call_graph(using_symbol_table=False) diff --git a/cldk/analysis/python/python_analysis.py b/cldk/analysis/python/python_analysis.py index 4b35e561..de248ad0 100644 --- a/cldk/analysis/python/python_analysis.py +++ b/cldk/analysis/python/python_analysis.py @@ -30,17 +30,11 @@ class PythonAnalysis: def __init__( self, - eager_analysis: bool, project_dir: str | Path | None, source_code: str | None, - analysis_backend_path: str | None, - analysis_json_path: str | Path | None, ) -> None: self.project_dir = project_dir self.source_code = source_code - self.analysis_json_path = analysis_json_path - self.analysis_backend_path = analysis_backend_path - self.eager_analysis = eager_analysis self.analysis_backend: TreesitterPython = TreesitterPython() def get_methods(self) -> List[PyMethod]: diff --git a/cldk/core.py b/cldk/core.py index 3c24697d..192bc059 100644 --- a/cldk/core.py +++ b/cldk/core.py @@ -27,6 +27,7 @@ from cldk.analysis.c import CAnalysis from cldk.analysis.java import JavaAnalysis from cldk.analysis.commons.treesitter import TreesitterJava +from cldk.analysis.python.python_analysis import PythonAnalysis from cldk.utils.exceptions import CldkInitializationException from cldk.utils.sanitization.java import TreesitterSanitizer @@ -118,6 +119,11 @@ def analysis( target_files=target_files, eager_analysis=eager, ) + elif self.language == "python": + return PythonAnalysis( + project_dir=project_path, + source_code=source_code, + ) elif self.language == "c": return CAnalysis(project_dir=project_path) else: diff --git a/cldk/models/java/models.py b/cldk/models/java/models.py index 100f338b..0e21c1d4 100644 --- a/cldk/models/java/models.py +++ b/cldk/models/java/models.py @@ -363,11 +363,15 @@ class JCompilationUnit(BaseModel): """Represents a compilation unit in Java. Attributes: + file_path (str): The path to the source file. + package_name (str): The name of the package for the comppilation unit. comments (List[JComment]): A list of comments in the compilation unit. imports (List[str]): A list of import statements in the compilation unit. type_declarations (Dict[str, JType]): A dictionary mapping type names to their corresponding JType representations. """ + file_path: str + package_name: str comments: List[JComment] imports: List[str] type_declarations: Dict[str, JType] diff --git a/tests/analysis/java/test_java_analysis.py b/tests/analysis/java/test_java_analysis.py index 0810764b..77e6d589 100644 --- a/tests/analysis/java/test_java_analysis.py +++ b/tests/analysis/java/test_java_analysis.py @@ -50,6 +50,24 @@ def test_get_symbol_table_is_not_null(test_fixture, analysis_json): ) assert analysis.get_symbol_table() is not None +def test_get_symbol_table_source_code(java_code): + """Should return a symbol table for source analysis with expected class/method count""" + + # Initialize the CLDK object with the project directory, language, and analysis_backend + cldk = CLDK(language="java") + analysis = cldk.analysis( + source_code=java_code, + analysis_backend_path=None, + eager=True, + analysis_level=AnalysisLevel.symbol_table, + ) + + # assert on expected class name and method count in the symbol table + expected_class_name = "com.acme.modres.WeatherServlet" + assert analysis.get_symbol_table() is not None + assert len(analysis.get_symbol_table().keys()) == 1 + assert expected_class_name in analysis.get_methods().keys() + assert len(analysis.get_methods().get(expected_class_name).keys()) == 9 def test_get_imports(test_fixture, analysis_json): """Should return NotImplemented for get_imports()""" diff --git a/tests/analysis/python/test_python_analysis.py b/tests/analysis/python/test_python_analysis.py index 19971315..c04c328e 100644 --- a/tests/analysis/python/test_python_analysis.py +++ b/tests/analysis/python/test_python_analysis.py @@ -67,7 +67,7 @@ def divide(self, a, b): def test_get_methods(): """Should return all of the methods""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) all_methods = python_analysis.get_methods() assert all_methods is not None @@ -79,7 +79,7 @@ def test_get_methods(): def test_get_functions(): """Should return all of the functions""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) all_functions = python_analysis.get_functions() assert all_functions is not None @@ -91,7 +91,7 @@ def test_get_functions(): def test_get_all_modules(tmp_path): """Should return all of the modules""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=tmp_path, source_code=None, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=tmp_path, source_code=None) # set up some temporary modules temp_file_path = os.path.join(tmp_path, "hello.py") @@ -111,7 +111,7 @@ def test_get_all_modules(tmp_path): def test_get_method_details(): """Should return the method details""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) method_details = python_analysis.get_method_details("add(self, a, b)") assert method_details is not None @@ -121,7 +121,7 @@ def test_get_method_details(): def test_is_parsable(): """Should be able to parse the code""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) code = "def is_parsable(self, code: str) -> bool: return True" is_parsable = python_analysis.is_parsable(code) @@ -134,7 +134,7 @@ def test_is_parsable(): def test_get_raw_ast(): """Should return the raw AST""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) raw_ast = python_analysis.get_raw_ast(PYTHON_CODE) assert raw_ast is not None @@ -144,7 +144,7 @@ def test_get_raw_ast(): def test_get_imports(): """Should return all of the imports""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) all_imports = python_analysis.get_imports() assert all_imports is not None @@ -156,7 +156,7 @@ def test_get_imports(): def test_get_variables(): """Should return all of the variables""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_variables() @@ -165,7 +165,7 @@ def test_get_variables(): def test_get_classes(): """Should return all of the classes""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) all_classes = python_analysis.get_classes() assert all_classes is not None @@ -178,7 +178,7 @@ def test_get_classes(): def test_get_classes_by_criteria(): """Should return all of the classes that match the criteria""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_classes_by_criteria() @@ -187,7 +187,7 @@ def test_get_classes_by_criteria(): def test_get_sub_classes(): """Should return all of the subclasses""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_sub_classes() @@ -196,7 +196,7 @@ def test_get_sub_classes(): def test_get_nested_classes(): """Should return all of the nested classes""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_nested_classes() @@ -205,7 +205,7 @@ def test_get_nested_classes(): def test_get_constructors(): """Should return all of the constructors""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_constructors() @@ -214,7 +214,7 @@ def test_get_constructors(): def test_get_methods_in_class(): """Should return all of the methods in the class""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_methods_in_class() @@ -223,7 +223,7 @@ def test_get_methods_in_class(): def test_get_fields(): """Should return all of the fields in the class""" - python_analysis = PythonAnalysis(eager_analysis=True, project_dir=None, source_code=PYTHON_CODE, analysis_backend_path=None, analysis_json_path=None) + python_analysis = PythonAnalysis(project_dir=None, source_code=PYTHON_CODE) with pytest.raises(NotImplementedError) as except_info: python_analysis.get_fields() diff --git a/tests/conftest.py b/tests/conftest.py index 5c9afdbe..9f4fdec7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -190,3 +190,24 @@ def test_fixture_binutils(): for directory in Path(test_data_path).iterdir(): if directory.exists() and directory.is_dir(): shutil.rmtree(directory) + +@pytest.fixture(scope="session", autouse=True) +def java_code() -> str: + """ + Returns sample Java source code for analysis. + + Yields: + str : Java code to be analyzed. + """ + # ----------------------------------[ SETUP ]---------------------------------- + # Path to your pyproject.toml + pyproject_path = Path(__file__).parent.parent / "pyproject.toml" + + # Load the configuration + config = toml.load(pyproject_path) + + # Access the test data path + test_data_path = config["tool"]["cldk"]["testing"]["sample-application"] + javafile = Path(test_data_path).absolute() / ("WeatherServlet.java") + with open(javafile) as f: + return f.read() diff --git a/tests/resources/java/application/WeatherServlet.java b/tests/resources/java/application/WeatherServlet.java new file mode 100644 index 00000000..1c456f74 --- /dev/null +++ b/tests/resources/java/application/WeatherServlet.java @@ -0,0 +1,283 @@ +package com.acme.modres; + +import com.acme.modres.db.ModResortsCustomerInformation; +import com.acme.modres.exception.ExceptionHandler; +import com.acme.modres.mbean.AppInfo; + +import java.io.BufferedReader; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.lang.management.ManagementFactory; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URI; +import java.net.URL; +import java.util.Hashtable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import javax.inject.Inject; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.MBeanInfo; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.annotation.WebServlet; + +// changed annotation +@WebServlet( + name={"weather"}, + urlPatterns={"/resorts/weather"} +) +public class WeatherServlet extends HttpServlet implements Serializable { // changed declaration + private static final long serialVersionUID = 1L; // added comment + + @Inject + private ModResortsCustomerInformation customerInfo; // mod + + private int newField; // added field + + // local OS environment variable key name. The key value should provide an API + // key that will be used to + // get weather information from site: http://www.wunderground.com + private static final String WEATHER_API_KEY = "WEATHER_API_KEY"; + + private static final Logger logger = Logger.getLogger(WeatherServlet.class.getName()); + + private static InitialContext context; + + MBeanServer server; + ObjectName weatherON; + ObjectInstance mbean; + + @Override + public void init() { + server = ManagementFactory.getPlatformMBeanServer(); + try { + weatherON = new ObjectName("com.acme.modres.mbean:name=appInfo"); + } catch (MalformedObjectNameException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + if (weatherON != null) { + mbean = server.registerMBean(new AppInfo(), weatherON); + } + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { + e.printStackTrace(); + } + context = setInitialContextProps(); + } + + @Override + public void destroy() { + if (mbean != null) { + try { + server.unregisterMBean(weatherON); + } catch (MBeanRegistrationException | InstanceNotFoundException e) { + // TODO Auto-generated catch block + System.out.println(e.getMessage()); + } + } + } + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + String methodName = "doGet"; + logger.entering(WeatherServlet.class.getName(), methodName); + + try { + MBeanInfo weatherConfig = server.getMBeanInfo(weatherON); + } catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) { + e.printStackTrace(); + } + + String city = request.getParameter("selectedCity"); + logger.log(Level.FINE, "requested city is " + city); + + String weatherAPIKey = System.getenv(WEATHER_API_KEY); + String mockedKey = mockKey(weatherAPIKey); + logger.log(Level.FINE, "weatherAPIKey is " + mockedKey); + + if (weatherAPIKey != null && weatherAPIKey.trim().length() > 0) { + logger.info("weatherAPIKey is found, system will provide the real time weather data for the city " + city); + getRealTimeWeatherData(city, weatherAPIKey, response); + } else { + logger.info( + "weatherAPIKey is not found, will provide the weather data dated August 10th, 2018 for the city " + city); + getDefaultWeatherData(city, response); + } + } + + private void getRealTimeWeatherData(String city, String apiKey, HttpServletResponse response) + throws ServletException, IOException { + String resturl = null; + String resturlbase = Constants.WUNDERGROUND_API_PREFIX + apiKey + Constants.WUNDERGROUND_API_PART; + + if (Constants.PARIS.equals(city)) { + resturl = resturlbase + "France/Paris.json"; + } else if (Constants.LAS_VEGAS.equals(city)) { + resturl = resturlbase + "NV/Las_Vegas.json"; + } else if (Constants.SAN_FRANCISCO.equals(city)) { + resturl = resturlbase + "/CA/San_Francisco.json"; + } else if (Constants.MIAMI.equals(city)) { + resturl = resturlbase + "FL/Miami.json"; + } else if (Constants.CORK.equals(city)) { + resturl = resturlbase + "ireland/cork.json"; + } else if (Constants.BARCELONA.equals(city)) { + resturl = resturlbase + "Spain/Barcelona.json"; + } else { + String errorMsg = "Sorry, the weather information for your selected city: " + city + + " is not available. Valid selections are: " + Constants.SUPPORTED_CITIES; + ExceptionHandler.handleException(null, errorMsg, logger); + } + + URL obj = null; + HttpURLConnection con = null; + try { + obj = URI.create(resturl).toURL(); + con = (HttpURLConnection) obj.openConnection(); + con.setRequestMethod("GET"); + } catch (MalformedURLException e1) { + String errorMsg = "Caught MalformedURLException. Please make sure the url is correct."; + ExceptionHandler.handleException(e1, errorMsg, logger); + } catch (ProtocolException e2) { + String errorMsg = "Caught ProtocolException: " + e2.getMessage() + + ". Not able to set request method to http connection."; + ExceptionHandler.handleException(e2, errorMsg, logger); + } catch (IOException e3) { + String errorMsg = "Caught IOException: " + e3.getMessage() + ". Not able to open connection."; + ExceptionHandler.handleException(e3, errorMsg, logger); + } + + int responseCode = con.getResponseCode(); + logger.log(Level.FINEST, "Response Code: " + responseCode); + + if (responseCode >= 200 && responseCode < 300) { + + BufferedReader in = null; + ServletOutputStream out = null; + + try { + in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine = null; + StringBuffer responseStr = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + responseStr.append(inputLine); + } + + response.setContentType("application/json"); + out = response.getOutputStream(); + out.print(responseStr.toString()); + logger.log(Level.FINE, "responseStr: " + responseStr); + } catch (Exception e) { + String errorMsg = "Problem occured when processing the weather server response."; + ExceptionHandler.handleException(e, errorMsg, logger); + } finally { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + in = null; + out = null; + } + } else { + String errorMsg = "REST API call " + resturl + " returns an error response: " + responseCode; + ExceptionHandler.handleException(null, errorMsg, logger); + } + } + + private void getDefaultWeatherData(String city, HttpServletResponse response) + throws ServletException, IOException { + DefaultWeatherData defaultWeatherData = null; + + try { + defaultWeatherData = new DefaultWeatherData(city); + } catch (UnsupportedOperationException e) { + ExceptionHandler.handleException(e, e.getMessage(), logger); + } + + ServletOutputStream out = null; + + try { + String responseStr = defaultWeatherData.getDefaultWeatherData(); + response.setContentType("application/json"); + out = response.getOutputStream(); + out.print(responseStr.toString()); + logger.log(Level.FINEST, "responseStr: " + responseStr); + } catch (Exception e) { + String errorMsg = "Problem occured when getting the default weather data."; + ExceptionHandler.handleException(e, errorMsg, logger); + } finally { + + if (out != null) { + out.close(); + } + + out = null; + } + } + + /** + * Returns the weather information for a given city + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + doGet(request, response); + } + + private static String mockKey(String toBeMocked) { + if (toBeMocked == null) { + return null; + } + String lastToKeep = toBeMocked.substring(toBeMocked.length() - 3); + return "*********" + lastToKeep; + } + + private String configureEnvDiscovery() { + + String serverEnv = ""; + + serverEnv += System.getProperty("wlp.server.name"); + serverEnv += System.getProperty("wlp.server.name"); + + return serverEnv; + } + + private InitialContext setInitialContextProps() { + + Hashtable ht = new Hashtable(); + + InitialContext ctx = null; + try { + ctx = new InitialContext(ht); + } catch (NamingException e) { + e.printStackTrace(); + } + + return ctx; + } +}