My plugin code slows down Eclipse, what could I improve? - java

I have a ITextViewer object and from that the StyledText widget in which Eclipse displays code. I want to hihglight one single line that is defined by the line number. Therefore I added a LineBackgroundListener to the StyledText object an wrote the following code:
private class HighlightLine implements LineBackgroundListener {
#Override
public void lineGetBackground(LineBackgroundEvent event) {
if (lineToHighlight != -1) {
int line = ((StyledText) event.widget).getLineAtOffset(event.lineOffset);
if (line == lineToHighlight) {
event.lineBackground = styledText.getSelectionBackground();
} else if (line == previousLineToHighlight) {
event.lineBackground = styledText.getBackground();
}
styledText.redraw();
styledText.update();
}
}
}
This work more or less fine, but the problem is, it very much slows down Eclipse. It's probably the case because it has to update the Widget very often and that's not optimal. However, I couldn't think of another way to to it after a lot of trials. Any idea?

You don't need these (expensive) calls:
styledText.redraw();
styledText.update();
lineGetBackground is called during the draw line operation so it does not need a redraw or update.
Note: If the StyledText control has any StyleRange ranges they may override the background.

Related

How to set the DataFlavor of a Component?

I'm not really sure if i have understood the principe of the DataFlavors correctly, but how can i actually set the DataFlavor(s) of a JComponent?
Every time i call the getDataFlavor method the output is this:
java.awt.datatransfer.DataFlavor[mimetype=application/x-java-file-list;representationclass=java.util.List]
My problem is, that i want to drag images from the desktop or any other place right into my JPanel. It's working through the DataFlavor.javaFileListFlavor, but is there no way that i can create a custom flavor which only accepts PNG and JPG Files for example?
(I know that it is actually possible to create Custom Flavors but i have no clue how i can "enable" the new created flavors for my components)
Or is there a way to ensure whether the dragged in Item is a PNG or JPG with the javaFileListFlavor?
I hope that i could explain my question well enough (I'm not a master of this language, but i'm trying my best ;) )
Or is there a way to ensure whether the dragged in Item is a PNG or JPG with the javaFileListFlavor?
Take a look at the Swing tutorial on Top Level Drop. It shows how to drag a file from the desktop to a JTextArea.
Take a look at the canImport(...) and importData(...) methods of the TransferHandler. The canImport(...) method currently only checks that you have a FileListFlavor. So you would need to add extra logic to see the actual File is a PNG or JPG.
If you look at the importData(...) method you can see how to get the File object from the TransferSupport object so you can implement the above check.
Edit:
but as described the Exception pops up
I just ignore the Exception. Here is the modified code for the tutorial that only allows your to copy ".java" files into the text area.
private TransferHandler handler = new TransferHandler() {
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
return false;
}
Transferable t = support.getTransferable();
try
{
java.util.List<File> l = (java.util.List<File>)t.getTransferData(DataFlavor.javaFileListFlavor);
File file = l.get(0);
String fileName = file.getName();
if (!file.getName().endsWith(".java"))
return false;
}
catch (Exception e)
{
// ignore
}
if (copyItem.isSelected()) {
boolean copySupported = (COPY & support.getSourceDropActions()) == COPY;
if (!copySupported) {
return false;
}
support.setDropAction(COPY);
}
return true;
}
Works fine for me using JDK8 on Winodow 7.

JPanel not visible

I've been having a problem with my program that has been driving me crazy and I cannot understand why this is happening. I have a GUI that when the "Add" button is pressed, a new system listing appears (Which is a class called SystemPanel that extends JPanel and cotains system details, which is created and then put into the Frame's main panel.)
To put it shortly, when I try to add a new SystemPanel, it does not appear for whatever reason. I have code using JSch that connects to the system and verifies whether its processes are online or not, but the line of code that does this is after the creation of the SystemPanel. It is only after the code for testing the processes of the system are executed that the SystemPanel becomes visible, and I can't understand why this is the case. Here is the code for adding a new SystemPanel:
public void actionPerformed(ActionEvent e) {
//If the "Add" button is pressed
if (e.getActionCommand() == "Add") {
PopupWindow popup = new PopupWindow(this);
popup.setVisible(true);
String[] results = popup.getResults();
if (results[0] != null && results[1] != null && results[2] != null && results[3] != null && results[4] != null) {
SystemPanel newSystem = new SystemPanel(this, results[0], results[1], results[2], results[3], results[4]);
systemsPanel.add(newSystem);
revalidate();
systemsList.add(newSystem);
System.out.println("Did the stuff");
boolean[] status = SystemChecker.checkOnline(results[0], results[1], results[2], results[3]);
}
}
}
The PopupWindow is a custom JDialog that allows the user to enter the required information which is returned in a String array and is used to create a new SystemPanel. The checkOnline function grabs the user's inputs and uses them to connect to the system and determine whether the processes are working or not, and returns the results into a boolean array, true for working, false for not.
What's even weirder is that I have another part of my program that reads from an .ini file to obtain existing systems and then creates SystemPanels based on the data that it reads. Through this method, the SystemPanels are added the way I want and work perfectly for some reason, even though the code for adding the panels is hardly any different. Code:
for (int i = 0; i < systems.size(); i++) {
SystemPanel newSystem = new SystemPanel(this, systems.get(i)[0], systems.get(i)[1], systems.get(i)[2], systems.get(i)[3], systems.get(i)[4]);
systemsPanel.add(newSystem);
revalidate();
systemsList.add(newSystem);
}
for (int i = 0; i < lineNum; i++) {
boolean[] status = SystemChecker.checkOnline(systems.get(i)[0], systems.get(i)[1], systems.get(i)[2], systems.get(i)[3]);
systemsList.get(i).updateIcons(status);
}
This code grabs the details from the file and then makes the SystemPanels based on those details. Here, all of the SystemPanels are added and show up before the connection is tested, which is what I want to happen when I add one normally.
Why is it that the SystemPanel doesn't appear until the connection is tested, even though the code for displaying the SystemPanel is executed before the connection test? Any help would be greatly appreciated, thanks.
Try it of the current event queue handling, on which actionPerformed is done.
public void actionPerformed(ActionEvent e) {
EventQueue.invokeLater(() -> { ... your code here ... });
}
Also you cannot add the same component to two parents, every component object has a single parent (container).
(Java 8 notation)

How to simplify long list of similar if statements?

I work as an automation engineer for my company. Recently, I wrote a piece of code that my manager absolutely would not accept.
I was asked to write some scripts for test cases involving different pieces of the GUI. The part of the code my manager would not accept was an if/else statement meant to check the current language of the prompt in the GUI.
I've been instructed to use Sikuli, and as such, it is very important that I know what language the application is currently set to so my scripts can click the correct buttons (which change depending on the language).
My thoughts were that the code iterates through the if/else statement and then points to the correct button. Example: The if/else statement determines that the "ok" button is currently the Suomi translation, so it will than click the correct button.
Here is an example of my code:
switch (button) {
case "ok":
if (s.exists("imagerepo/language/catalan_ok.png") != null) {
s.click("imagerepo/language/catalan_ok.png");
} else if (s.exists("imagerepo/language/suomi_ok.png") != null) {
s.click("imagerepo/language/suomi_ok.png");
} else if (s.exists("imagerepo/language/italian_ok.png") != null) {
s.click("imagerepo/language/italian_ok.png");
} else if (s.exists("imagerepo/language/portuguese_ok.png") != null) {
s.click("imagerepo/language/portuguese_ok.png");
} else if (s.exists("imagerepo/language/english_ok.png") != null) {
s.click("imagerepo/language/english_ok.png");
} else if (s.exists("imagerepo/language/dutch_ok.png") != null) {
s.click("imagerepo/language/dutch_ok.png");
} else if (s.exists("imagerepo/language/spanish_ok.png") != null) {
s.click("imagerepo/language/spanish_ok.png");
} else if (s.exists("imagerepo/language/french_ok.png") != null) {
s.click("imagerepo/language/french_ok.png");
} else if (s.exists("imagerepo/language/latina_ok.png") != null) {
s.click("imagerepo/language/latina_ok.png");
} else if (s.exists("imagerepo/language/chinese_ok.png") != null) {
s.click("imagerepo/language/chinese_ok.png");
}
break;
...etc..
My only gripe with the above code is that it is pretty ugly. Functionally it does exactly what I'd like it to, 100% of the time.
EDIT: I figure that having a switch that adapts to the potentially changing button would be better than having 10 switches for the same button. Arguably, against what I just said, if I'm writing the scripts, I will always know what language the system is going to be in.
If this is an example of poor code, what could I do instead to determine which "form" of the button I need to press?
It would be worth noting now that the answer I'm looking for does not actually pertain to testing at all, but rather, how do I optimally perform the function of that if/else block above?
Something like:
String[] languages = {
"catalan_ok.png",
"suomi_ok.png",
//...
}
for (String base : languages) {
String file = String.format("imagerepo/language/%s", base);
if (s.exists(file) != null) {
s.click(file);
break;
}
}
perhaps? Not tested.
I'm assuming that this is for internationalization so you could use a properties file for each language and then get the image path like this
ResourceBundle bundle = ResourceBundle.getBundle( "messages", userLocale );
s.click(bundle.getString("image"));

UndoableEditListener never called in JTextPane/JTextArea

I am trying to attach an UndoableEditListener to a JTextPane or JTextArea that queues up edits into an UndoManager.
textPane.getDocument().addUndoableEditListener(new UndoableEditListener() {
#Override
public void undoableEditHappened(UndoableEditEvent event) {
undoQueue.addEdit(event.getEdit());
}
});
But undoableEditHappened is never called when I type "aaa" in the text window.
Thinking it's Java's fault, not mine, I crack AbstractDocument.class open with Eclipse debugger to watch the event trigger. It has a private listeners array. AbstractDocument stores all its listeners in odd indices in the listeners array, with the listeners' type Class<>'s in the even indices.
protected void fireUndoableEditUpdate(UndoableEditEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == UndoableEditListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
((UndoableEditListener) listeners[i + 1]).undoableEditHappened(e);
}
}
}
See the line if (listeners[i] == UndoableEditListener.class)? When I add the undo change listener, the debugger shows listeners containing my listener, along with UndoableEditListener.class in the index before it. But, when the debugger comes to that if-statement, all the even indices in the array listeners show as DocumentListener.class in the debugger. Consequently, the if-statement is always false and the listener never called.
What the heck? Is this a Java 8 bug? Or am I missing a step the examples forgot to mention?
The problem was in the JTextPane. I was overriding its setText method to force it to call read, the alternative to setText that normalizes all kinds of newline while remembering them. But JTextPane.read appears to not trigger an UndoableEditEvent on the document.
If I leave setText alone, then UndoManager.undo works.

Strange pointer issue when trying to strongly connect objects

In my program I have two classes, one called GlassPiece, and one called TrackerChip.
These two objects are always "strongly connected", that is, no two GlassPieces can share a TrackerChip, and no two TrackerChips can share a GlassPiece. Therefore in my setter methods, I need to take care to disconnect any old references hanging around, as so:
public class TrackerChip
{
GlassPiece linkedGlassPiece;
public void setGlassPiece(GlassPiece newGlassPiece)
{
GlassPiece oldGlassPiece = linkedGlassPiece;
linkedGlassPiece = newGlassPiece;
if(oldGlassPiece != null)
{
oldGlassPiece.setTrackerChip(null); //disconnect old GlassPiece
}
if(linkedGlassPiece != null && linkedGlassPiece.getTrackerChip() != this)
{
linkedGlassPiece.setTrackerChip(this); //update counterpart
}
}
}
and the method GlassPiece.setTrackerChip(TrackerChip) works exaxctly the same way.
The thing is, the above code doesn't actually work, and strange stuff happens when trying to manage linking between several different GlassPieces and TrackerChips. However, if I replace the last part with:
if(newGlassPiece != null && newGlassPiece.getTrackerChip() != this)
{
newGlassPiece.setTrackerChip(this);
}
Then everything works properly. This seems very strange to me (all I did was replaced linkedGlassPiece, the instance variable, with newGlassPiece, the parameter). But early in the method I set the references equal to each other! Why does the first method not work?
P.S. I can confirm there is no infinite loop in the method.
As for why this isn't working, you're right, it won't hit an endless loop, but it's not going to do what you expect.
You enter setGlassPiece, linkedGlassPiece for this object is set to the value of newGlassPiece.
Then it calls setTrackerChip(null) on the oldGlassPiece.
The oldGlassPiece still has a reference to the original TrackerChip, so it calls setGlassPiece(null), which sets linkedGlassPiece to null that you just set on the TrackerChip, and calls setTrackerChip(null) on the NEW GlassPiece as well.
I honestly can't think of a way to get it to work the way you're going. You would have to add some additional parameters such that it would no longer be re-entrant. Namely, when you call setTrackerChip on the oldGlassPiece, it's not going to turn around and call the same TrackerChip back setting its reference to null. Perhaps just a boolean flag that would indicate that it should not null out the second level references.
Here's some code:
public class TrackerChip
{
GlassPiece linkedGlassPiece;
public void setGlassPiece(GlassPiece newGlassPiece)
{
setGlassPiece(newGlassPiece, true);
}
public void setGlassPiece(GlassPiece newGlassPiece, boolean reentrant)
{
GlassPiece oldGlassPiece = linkedGlassPiece;
linkedGlassPiece = newGlassPiece;
if(reentrant && oldGlassPiece != null)
{
oldGlassPiece.setTrackerChip(null, false); //disconnect old GlassPiece
}
if(linkedGlassPiece != null && linkedGlassPiece.getTrackerChip() != this)
{
linkedGlassPiece.setTrackerChip(this); //update counterpart
}
}
}
Instead of taking this approach, I would recommend just having a pair of static HashMaps that manage the relationships. That would probably be far simpler. There could be thread safety issues if your use case is not single threaded, but you'd just need to synchronize the method that sets it up. Maybe create a relationship management object as follows:
public class RelationshipMgr {
HashMap<GlassPiece, TrackerChip> gpMap;
HashMap<TrackerChip, GlassPiece> tcMap;
public void setRelationship(GlassPiece gp, TrackerChip tc) {
gpMap.put(gp, tc);
tcMap.put(tc, gp);
}
}
Actually, in the Google Guava library, there is even a class ready to use for this sort of thing called BiMap, check it out.
If there is always a one to one relationship, it might be worthwhile to consider merging those classes into one.
This should work I think:
public class TrackerChip {
GlassPiece linkedGlassPiece;
public void setGlassPiece(GlassPiece newGlassPiece) {
if (linkedGlassPiece == newGlassPiece) {
return;
}
if (linkedGlassPiece != null) {
GlassPiece tmp = linkedGlassPiece;
linkedGlassPiece = null;
tmp.setTrackerChip(null);
}
if (newGlassPiece != null) {
linkedGlassPiece = newGlassPiece;
linkedGlassPiece.setTrackerChip(this);
}
}
}

Categories