INRIA SPOON INNER METHODS CALLING CONSTRUCTORS - java

I am trying to extract the calls from the method run() to the constructors. Here is the code I am trying to parse
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Create the two text areas
TextAreaFigure ta = new TextAreaFigure();
ta.setBounds(new Point2D.Double(10,10),new Point2D.Double(100,100));
TextAreaFigure tb = new TextAreaFigure();
tb.setBounds(new Point2D.Double(210,110),new Point2D.Double(300,200));
// Create an elbow connection
ConnectionFigure cf = new LineConnectionFigure();
cf.setLiner(new ElbowLiner());
// Connect the figures
cf.setStartConnector(ta.findConnector(Geom.center(ta.getBounds()), cf));
cf.setEndConnector(tb.findConnector(Geom.center(tb.getBounds()), cf));
// Add all figures to a drawing
Drawing drawing = new DefaultDrawing();
drawing.add(ta);
drawing.add(tb);
drawing.add(cf);
// Show the drawing
JFrame f = new JFrame("My Drawing");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400,300);
DrawingView view = new DefaultDrawingView();
view.setDrawing(drawing);
f.getContentPane().add(view.getComponent());
f.setVisible(true);
}
});
}
Here is the code I am using to extract the calls from method run() to the constructors. The problem that I have is that the last line: String constructorClassName= cons.getExecutable().getDeclaringType().toString(); is returning the wrong class name, instead of getting "jhot.draw.TextAreaFigure()" as the name I am getting "jhot.mini.samples.TextAreaFigure()". The file that I am parsing is located under "jhot.mini.samples" while the constructor is declared within "jhot.draw.TextAreaFigure()". I am not sure if this is a bug in spoon or if I am using the wrong API to retrieve the constructor calls.
for(CtMethod<?> method :clazz.getMethods()) {
List<CtConstructorCall> ctNewClasses = method.getElements(new TypeFilter<CtConstructorCall>(CtConstructorCall.class));
for( CtConstructorCall myclass: ctNewClasses) {
//CONSTRUCTOR
if(myclass instanceof CtConstructorCall<?>) {
System.out.println("yes");
List<CtMethod> methoddeclared = myclass.getElements(new TypeFilter<CtMethod>(CtMethod.class));
for(CtMethod<?> meth: methoddeclared) {
methodinside=meth.getSignature();
methodinsideclass=clazz.getQualifiedName();
String mymethod=methodinsideclass+"."+methodinside;
ResultSet methodsinside = st.executeQuery("SELECT methods.* from methods where methods.fullmethod='"+mymethod+"'");
//while(callingmethodsrefined.next()){
if(methodsinside.next()) {
MethodIDINSIDE = methodsinside.getString("id");
CLASSNAMEINSIDE = methodsinside.getString("classname");
CLASSIDINSIDE = methodsinside.getString("classid");
//System.out.println("CALLEE METHOD ID: "+ CALLEEID);
}
List<CtConstructorCall> constructors = meth.getElements(new TypeFilter<CtConstructorCall>(CtConstructorCall.class));
for(CtConstructorCall<?> cons: constructors) {
String constructorClassName= cons.getExecutable().getDeclaringType().toString();
}
}
}
}

I am not sure if this is a bug in spoon or if I am using the wrong API to retrieve the constructor calls.
I'm one of the contributor of Spoon. It looks to me that you're using the right API, but I'm not sure because your example looks a bit messy here.
I think it would be easier if you open an issue on Spoon Github repository and specify:
the project you're working on if it's open-source
how you launch Spoon (the version of Spoon, arguments, etc)
what do you expect exactly
Then we could investigate to check exactly what happens there. Thanks!

Related

INRIA SPOON nested methods and nested methodcalls

I am trying to use SPOON developed by INRIA to retrieve all the methods in a program as well as all the method calls. I am able to do so for normal methods, however, I am not able to retrieve nested methods and I am not able to retrieve nested method calls either.
Here is a fragment of code that I am parsing, In this case, I would like spoon to collect the method run() which is nested without the main, I would also like to retrieve the call from run to the constructor of the class ElbowLiner, could you please give me directions on how to achieve this. I used getAll(true) to retrieve everything including the nested method calls but it did not work, I was not able to retrieve run() in the code fragment below and I was not able to retrieve the method call either from run() to the constructor of ElbowLiner
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Create the two text areas
TextAreaFigure ta = new TextAreaFigure();
ta.setBounds(new Point2D.Double(10,10),new Point2D.Double(100,100));
TextAreaFigure tb = new TextAreaFigure();
tb.setBounds(new Point2D.Double(210,110),new Point2D.Double(300,200));
// Create an elbow connection
ConnectionFigure cf = new LineConnectionFigure();
cf.setLiner(new ElbowLiner());
// Connect the figures
cf.setStartConnector(ta.findConnector(Geom.center(ta.getBounds()), cf));
cf.setEndConnector(tb.findConnector(Geom.center(tb.getBounds()), cf));
// Add all figures to a drawing
Drawing drawing = new DefaultDrawing();
drawing.add(ta);
drawing.add(tb);
drawing.add(cf);
// Show the drawing
JFrame f = new JFrame("My Drawing");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(400,300);
DrawingView view = new DefaultDrawingView();
view.setDrawing(drawing);
f.getContentPane().add(view.getComponent());
f.setVisible(true);
}
});
}
The easiest way in Spoon to retrieve all methods from a model is to use a processor of CtMethod you can try a code like this one:
public class MyProcessForMethods extends AbstractProcessor<CtMethod> {
public void process(CtMethod myMethod) {
System.out.println(mymethod.getSimpleName());
}
}
and to use it:
Launcher launcher = new Launcher();
launcher.addInputResource("/path/to/your/source");
launcher.addProcessor(new MyProcessForMethods());
launcher.run();
The method process() will be called each time a new CtMethod is find in the model: then it will process as well method nested from inner types and normal methods.
Don't hesitate to open an issue on Spoon Github repository and to provide more insight on how you're using Spoon right now.

IntelliJ Plugin Development: insert new code after all fields within a class

I'm working a plugin that generates method within a class.
The problem is that the code I generate gets inserted randomly between other fields and methods. EG. I have 4 fields and my method gets inserted between the second and the third field breaking the field section in two parts.
Question: How to insert the new code after fields section?
Here is the code I'm using:
MyMembersHandlerBase extends GenerateMembersHandlerBase {
#Override
protected List<? extends GenerationInfo> generateMemberPrototypes(
PsiClass psiClass, ClassMember[] members) {
PsiMethod method1 = // method generation logic
PsiMethod method2 = // ...
return asList(
new PsiGenerationInfo(method1),
new PsiGenerationInfo(method2),
...
);
}
#Override
protected ClassMember[] getAllOriginalMembers(PsiClass psiClass) {
// ...
}
}
Update: I've found "Rearrange Code" feature in UI. Probably invoking it programmatically would solve my problem, but I haven't found so far how to do that.
The action can be called this way:
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
ActionManager actionManager = ActionManager.getInstance();
AnAction rearrangeAction = actionManager.getAction("RearrangeCode");
DataContext dataContext = DataManager.getInstance()
.getDataContext(editor.getContentComponent());
Presentation presentation = rearrangeAction.getTemplatePresentation();
rearrangeAction.actionPerformed(
AnActionEvent.createFromDataContext("", presentation, dataContext)
);
But IMHO more correct is to search AST for first method or end of declaration and insert method in right place.

How to transport information from one class to another in order to show it without using static?

I am programming an application that deals with orders from a database. It has several pages, a navigation, a header that always should show information about the actual order you are working with and a content area, in which the details of said order get shown:
My MainProgram extends a JFrame and contains a CardLayout, in which the other pages are hosted, so when the user clicks on the page in the navigation, only the view of the content-area changes. Logo, header and the navigation stay the same. The header keeps displaying the order number.
As there are several different pages that contain details about the same order, I need to "send / transfer" information about the order from one page to the other so I can show some information in the header and in the content area from the order object.
But I am not getting this to work as intended, mostly to my misunderstand of static and when to use it, where objects get created exactly and also the complexity of my program: I am using a class that is intended for the navigation and therefore should also handle
the information transfer from one page to the other.
Since I am using a database, creating a MVCE will be hard, so instead I will show the important parts of my program.
MainProgram.java
Here the navigation and the content panel (centerPanel) get created, also the CardLayout. centerPanel and the CardLayout are static, so I can call this from other classes and switch the page that is shown (probably not a good idea?):
NavigationPanel navigationPanel = new NavigationPanel();
public static JPanel centerPanel = new JPanel();
public static CardLayout contentCardsLayout = new CardLayout();
I create the pages and put them into my CardLayout:
OverviewPage overviewPage = new OverviewPage();
BasicDataPage basicDataPage = new BasicDataPage();
centerPanel.setLayout(contentCardsLayout);
overviewPage.setName("overviewPage");
basicDataPage.setName("basicDataPage");
centerPanel.add(overviewPage, "overviewPage");
centerPanel.add(basicDataPage, "basicDataPage");
The main method, where I create a MainProgram object:
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
MainProgram window = new MainProgram();
window.setVisible(true);
window.initialize();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
OverviewPage.java
The overview page contains a JTable which gets populated from a database. If the user double-clicks an entry, he gets transfered to the BasicDataPage where he can see the details of the order.
But in order to show the details, I need to somehow transfer the information of the order object into the target class and thats the point I am struggling with!
// I tried several things like object into constructor, static object, creating a method etc...
if (mouseEvent.getClickCount() == 2 && row != -1) {
String workNumberOfOrderObject = (String) table.getValueAt(row, 0);
OrderObject orderObject = GetOrderObject.getOrderObjectFromDatabase(workNumberOfOrderObject);
BasicDataPage basicDataPage = new BasicDataPage();
basicDataPage.recieveOrderObject(orderObject);
workNumberPanel.recieveOrderObject(orderObject);
workNumberPanel.setTxtWorkNumber(workNumberOfOrderObject);
MainProgram.contentCardsLayout.show(MainProgram.centerPanel, "basicDataPage");
}
I tried "sending" the order object to the BasicDataPage via the constructor and set the text in the JTextFields in the BasicDataPage accordingly. This did not work, the textfields simply stayed empty altough I can System.out.println(orderObject.toString()) the recieved object.
BasicDataPage.java
I also tried creating a method receiveOrderObject that I use in the OverviewPage, which should set the textfields of the basicDataPage AND the workNumberPanel, but the fields stay empty:
WorkNumberPanel workNumberPanel = new WorkNumberPanel();
JTextField txtCarWidth = new JTextField(TEXTFIELD_LENGTH);
JTextField txtCarDepth = new JTextField(TEXTFIELD_LENGTH);
JTextField txtCarHeight = new JTextField(TEXTFIELD_LENGTH);
public void recieveOrderObject(OrderObject orderObject){
txtCarDepth.setText(orderObject.getCar_depth());
}
Before posting my question I've read several Q/As here on SO like this:
Accessing UUID from another class in Java ... suggesting to use static for global variables.
I know that static variables are class variables, that all instances can use and only one version exists of. So I tried to send a static object from one class to the other.
But since I am using JTextFields, I had to mix static and non-static content, which either did not work at all or the textfields disappeared.
I have the feeling that I am getting a very basic concept in java wrong, so any help, no matter in which direction, is appreciated!
EDIT:
Based on Reşit Dönüks answer, I was able to fill the textfields by making BasicDataPage and loadBasicData(orderObject) in MainProgram static. Now I can do MainProgram.loadBasicData(orderObject); ... and the textfields in the BasicDataPage get filled as intended.
Is this a valid approach or do I get problems for using static for GUI-Elements? ..... Don't!
I realized that, your are creating BasicDataPage in each double click.
if (mouseEvent.getClickCount() == 2 && row != -1) {
String workNumberOfOrderObject = (String) table.getValueAt(row, 0);
OrderObject orderObject = GetOrderObject.getOrderObjectFromDatabase(workNumberOfOrderObject);
BasicDataPage basicDataPage = new BasicDataPage();
This is the main problem. Do not create BasicDataPage there, just reach the created instance and set the order object to that. My solution is below.
public class MainProgram implements OrderView{
//remove statics here
private JPanel centerPanel = new JPanel();
private CardLayout contentCardsLayout = new CardLayout();
private BasicDataPage basicPage;
public MainProgram() {
//other codes
OverviewPage overviewPage = new OverviewPage();
basicPage = new BasicDataPage();
centerPanel.setLayout(contentCardsLayout);
overviewPage.setName("overviewPage");
basicDataPage.setName("basicDataPage");
centerPanel.add(overviewPage, "overviewPage");
centerPanel.add(basicPage, "basicDataPage");
//oher codes
}
#Override
public void loadOrder(OrderObject order) {
basicPage.recieveOrderObject(orderObject);
contentCardsLayout.show(centerPanel, "basicDataPage");
}
}
public interface OrderView {
public void loadOrder(OrderObject order);
}
public class OverviewPage {
OrderView orderView;
public OverviewPage(OrderView orderView) {
this.orderView = orderView;
}
//in ActionPerformed
if (mouseEvent.getClickCount() == 2 && row != -1) {
String workNumberOfOrderObject = (String) table.getValueAt(row, 0);
OrderObject orderObject = GetOrderObject.getOrderObjectFromDatabase(workNumberOfOrderObject);
orderView.loadOrder(orderObject);
workNumberPanel.recieveOrderObject(orderObject);
workNumberPanel.setTxtWorkNumber(workNumberOfOrderObject);
}
}
As pointed already, Singleton is the way to go. I would just like to point out a mistake in the code provided in the answer before.
private static MainFrameinstance = null;
Rename MainFrameinstance to instance or vice-versa; because the same variable is checked by the getInstance() method.

Getting the declaring class of an instance: possible?

Is there any way to retrieve the declaring class of an instance at runtime?
For example:
public class Caller {
private JFrame frame = new JFrame("Test");
private JButton button = new JButton("Test me");
private Callee callee = new Callee();
public Caller() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
button.addActionListener(callee.getListener());
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new Caller();
}
}
The callee:
public class Callee {
public ActionListener getListener() {
return new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
/* Get the class "Caller" here and invoke its methods */
/* Something along the lines of: */
Object button = e.getSource();
button.getOwnerClass(); //This would return the type Caller
}
};
}
}
"getOwnerClass()" is an imaginary method. Is there a way to get a result similar to that?
There is nothing in the standard API that allows you to get this information. It's a bit unclear what you mean with 'declaring class' or 'owner class' but for this sake of this answer I'll assume that it is the class whose code created the instance of the object (that you want the owner class from).
This information is not stored by the JVM by default.
But, using the heap profiler that is packaged along with the JDK distribution, you can record the stacktrace of the point at which objects are allocated, and this information can be written to a file on various points in time.
That still doesn't give you an API call to retrieve the information, but it shows that it is technically possible to record this type of information.
I search a bit on Google and found that someone did create an API that uses the same basic technique as the heap profiler (the java.lang.instrumentation package/JVMTI interface)
Open source project: java-allocation-instrumenter
With a bit of work you should be able to build something with it.
The site has a nice example:
AllocationRecorder.addSampler(new Sampler() {
public void sampleAllocation(int count, String desc, Object newObj, long size) {
System.out.println("I just allocated the object " + newObj +
" of type " + desc + " whose size is " + size);
if (count != -1) { System.out.println("It's an array of size " + count); }
}
});
Instead of printing, you should get the stacktrace using new Exception().getStackTrace(), remove the first few StackTraceElement objects that refer to the sampler and the API classes, and then call StackTraceElement.getClassName() to get the name of the class that created the object instance, in other words, your OwnerClass.

Taking a String and making an object from a class that String represents in Java

Scenario:
I have two classes named ListLayout and GridLayout which both implement CustomLayout.
The user enters a String representing the layout they wish to use ("ListLayout" for example)
How can I create a ListLayout object based on the string entered by the user? I would need to be equivalent to just doing this:
CustomLayout layout = new ListLayout();
Ideally I would need to find a solution which would allow me to check if the String entered corresponds to a predefined class which implements CustomLayout before actually making the object (because it will throw an error if it doesn't exist and I don't check beforehand).
This is really getting me thinking.... thanks in advance for your help
If you don't want to use reflection here, a map of factories could be the right thing:
interface LayoutFactory {
public CustomLayout makeLayout();
}
Map<String, LayoutFactory> factories = new HashMap<String, LayoutFactory>();
factories.put("GridLayout", new LayoutFactory() {
public CustomLayout makeLayout() { return new GridLayout(); }
});
factories.put("ListLayout", new LayoutFactory() {
public CustomLayout makeLayout() { return new ListLayout(); }
});
String layoutName = ...; // user input
CustomLayout l = factories.get(layoutName).makeLayout();
Of course, you also should handle the case where the user did give an unknown layout name (factories.get then returns null).
Do something like this:
String userInput = //get user's input
while(!userInput.equalsIgnoreCase("ListLayout") && !userInput.equalsIgnoreCase("gridLayout")){
System.out.println("Please enter a valid option");
userInput = //get user's input again
}
CustomLayout layout;
if(userInput.equalsIgnoreCase("ListLayout")
layout = new ListLayout();
else
layout = new GridLayout();
I would personally not recommend using reflection; as refactoring becomes a pain.
Better would be to do a string check and instantiate the correct class.
For example
if(userInput.equals("ListLayout")) {
CustomLayout layout = new ListLayout();
} else if (userInput.equals("GridLayout")) {
CustomLayout layout = new GridLayout();
}
This can also be implemented using java reflection as others pointed out. But if you want to refactor the code later on (say using eclipse refactoring for example), then the reflection code will not be auto-refactored. For example, if you used reflection and if you changed the class name of ListLayout to FancyListLayout, and do eclipse auto refactoring, the reflection code will be left untouched and your code will break.
This is the code used to obtain an instance of a class if you have a String with the fully qualified name:
try {
CustomLayout layout = (CustomLayout) Class.forName("your.package.ListLayout").newInstance();
} catch (Exception ex) {
}
The exception can be of type: LinkageError, ExceptionInInitializerError, ClassNotFoundException, IllegalAccessException, InstantiationException and SecurityException and it is recommended to have catch clauses for each of them if you want to handle them differently.
Two steps :
Find if text entered by user corresponds to an existing class (this can be done using the reflections framework, I guess)
Instanciate an object of that class, which is usually done using Class.forName(String)
Ok. What you should be looking into is called Reflection (wiki on reflection) and java offers a rich API for that. THis basically allows you to generate objects from a String and catch the execption if there is no such class accordingly. This has however some drawbacks, please check on the API for further reference.
Cheers!
Reflection is the natural answer, but you could create a Factory for it.
public class CustomLayoutFactory {
public CustomLayout createInstance(String layoutName) {
if("ListLayout".equals(layoutName) {
return new ListLayout();
} else if("GridLayout".equals(layoutName) {
return new GridLayout();
}
return null;
}
}
While not the most elegant solution, it is useful in cases where the SecurityManager is too restrictive for reflection.
Reading all the answers I'd like to strongly recommend against the reflection based ones, since you will have to be very careful in renaming your classes afterwards.
Also instead of:
if ("ListLayout".equals(userInput)) {
return new ListLayout();
}
you can add a protected field inside your base Layout class:
public abstract class Layout {
protected String userInputName;
}
and modifying its extenders like so:
public class ListLayout {
public ListLayout() {
userInputName = "listLayout"; // set protected field
}
}
Then you can do:
for (Layout l : setOfAllLayouts) {
if (userInput.equals(l.getInputName)) {
return l.clone();
}
}
This ought to do it:
Class.forName(userInput).newInstance();
You can then use instanceof to test for what class you ended up with. However, I'd test the value of userInput before doing this, to make sure it's a recognized class name. You might also need to prepend the package name if your users just input the simple class name.

Categories