From aa8a7c03c3409b4d117c049678d7f34d60bc7144 Mon Sep 17 00:00:00 2001 From: skylot Date: Tue, 26 Mar 2019 14:21:47 +0300 Subject: [PATCH] style: enforce strict style rules with editorconfig (PR #510) --- .editorconfig | 22 + build.gradle | 175 +++---- .../src/main/java/jadx/cli/JadxCLIArgs.java | 2 +- jadx-cli/src/main/resources/logback.xml | 24 +- .../src/main/java/jadx/api/JadxArgs.java | 3 +- .../src/test/java/jadx/NotYetImplemented.java | 8 +- .../generics/TestImportGenericMap.java | 27 +- jadx-core/src/test/resources/logback.xml | 16 +- .../java/jadx/gui/treemodel/JPackage.java | 8 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 67 +-- .../jadx/gui/ui/codearea/LineNumbers.java | 452 +++++++++--------- .../src/main/java/jadx/gui/utils/NLS.java | 216 ++++----- jadx-samples/build.gradle | 50 +- .../java/jadx/samples/TestInitializers.java | 21 +- .../main/java/jadx/samples/TestInner3.java | 9 +- 15 files changed, 550 insertions(+), 550 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..636b7e2cf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# EditorConfig is awesome: https://EditorConfig.org +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +indent_style = tab +tab_width = 4 + +charset = utf-8 +trim_trailing_whitespace = true + +[*.xml] +indent_size = 1 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.bat] +end_of_line = crlf diff --git a/build.gradle b/build.gradle index db3f86c15..ba3c624da 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { - id 'org.sonarqube' version '2.7' - id 'com.github.ben-manes.versions' version '0.21.0' + id 'org.sonarqube' version '2.7' + id 'com.github.ben-manes.versions' version '0.21.0' + id 'org.ec4j.editorconfig' version '0.0.3' } ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" @@ -8,125 +9,137 @@ version = jadxVersion println("jadx version: ${jadxVersion}") allprojects { - apply plugin: 'java' - apply plugin: 'groovy' - apply plugin: 'jacoco' + apply plugin: 'java' + apply plugin: 'groovy' + apply plugin: 'jacoco' - version = jadxVersion + version = jadxVersion - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 - if (!"$it".contains(':jadx-samples:')) { - options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation' - } - } + if (!"$it".contains(':jadx-samples:')) { + options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation' + } + } - compileJava { - options.encoding = "UTF-8" - } + compileJava { + options.encoding = "UTF-8" + } - jar { - version = jadxVersion - manifest { - mainAttributes('jadx-version': jadxVersion) - } - } + jar { + version = jadxVersion + manifest { + mainAttributes('jadx-version': jadxVersion) + } + } - dependencies { - compile 'org.slf4j:slf4j-api:1.7.26' + dependencies { + compile 'org.slf4j:slf4j-api:1.7.26' - testCompile 'ch.qos.logback:logback-classic:1.2.3' - testCompile 'org.hamcrest:hamcrest-library:2.1' - testCompile 'org.mockito:mockito-core:2.25.1' + testCompile 'ch.qos.logback:logback-classic:1.2.3' + testCompile 'org.hamcrest:hamcrest-library:2.1' + testCompile 'org.mockito:mockito-core:2.25.1' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1' - } + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1' + } - test { - useJUnitPlatform() - } + test { + useJUnitPlatform() + } - repositories { - mavenLocal() - mavenCentral() - jcenter() - google() - } + repositories { + mavenLocal() + mavenCentral() + jcenter() + google() + } - jacoco { - toolVersion = "0.8.2" - } - jacocoTestReport { - reports { - xml.enabled = true - html.enabled = true - } - } + jacoco { + toolVersion = "0.8.2" + } + jacocoTestReport { + reports { + xml.enabled = true + html.enabled = true + } + } } sonarqube { - properties { - property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*' - property 'sonar.coverage.exclusions', '**/jadx/gui/**/*' - } + properties { + property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*' + property 'sonar.coverage.exclusions', '**/jadx/gui/**/*' + } +} + +editorconfig { + excludes = ['gradle/' + , 'jadx-test-app/test-app' // ignore issues in submodule + , '**/out/' // IntelliJ Idea build dirs + , '**/certificate-test/' // binary test files (.RSA) + , '**/*.svg' + , '**/*.arsc' + ] } dependencyUpdates.resolutionStrategy = { - componentSelection { rules -> - rules.all { ComponentSelection selection -> - boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier -> - selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ - } - if (rejected) { - selection.reject('Release candidate') - } - } - } + componentSelection { rules -> + rules.all { ComponentSelection selection -> + boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier -> + selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ + } + if (rejected) { + selection.reject('Release candidate') + } + } + } } task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) { - destinationDir file("$buildDir/jadx") - ['jadx-cli', 'jadx-gui'].each { - from tasks.getByPath(":${it}:installDist").destinationDir - } + destinationDir file("$buildDir/jadx") + ['jadx-cli', 'jadx-gui'].each { + from tasks.getByPath(":${it}:installDist").destinationDir + } } task pack(type: Zip, dependsOn: copyArtifacts) { - destinationDir buildDir - archiveName "jadx-${jadxVersion}.zip" - from copyArtifacts.destinationDir + destinationDir buildDir + archiveName "jadx-${jadxVersion}.zip" + from copyArtifacts.destinationDir } task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') { - group 'jadx' - description = 'Copy exe to build dir' - destinationDir buildDir - from tasks.getByPath('jadx-gui:createExe').outputs - include '*.exe' + group 'jadx' + description = 'Copy exe to build dir' + destinationDir buildDir + from tasks.getByPath('jadx-gui:createExe').outputs + include '*.exe' } task dist(dependsOn: [pack, copyExe]) { - group 'jadx' - description = 'Build jadx distribution zip' + group 'jadx' + description = 'Build jadx distribution zip' } task samples(dependsOn: 'jadx-samples:samples') { - group 'jadx' + group 'jadx' } task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') { - group 'jadx' + group 'jadx' } task cleanBuildDir(type: Delete) { - group 'jadx' - delete buildDir + group 'jadx' + delete buildDir } -build.dependsOn(dist, samples) +check.dependsOn editorconfigCheck + +test.dependsOn(samples) clean.dependsOn(cleanBuildDir) diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 09e7c5a0e..d66b929fc 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -125,7 +125,7 @@ public class JadxCLIArgs { } if (verbose) { ch.qos.logback.classic.Logger rootLogger = - (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); // remove INFO ThresholdFilter Appender appender = rootLogger.getAppender("STDOUT"); if (appender != null) { diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml index 6a1e61641..1b666db4a 100644 --- a/jadx-cli/src/main/resources/logback.xml +++ b/jadx-cli/src/main/resources/logback.xml @@ -1,16 +1,14 @@ + + + INFO + + + %-5level - %msg%n + + - - - INFO - - - %-5level - %msg%n - - - - - - - + + + diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 003980905..6011cc2aa 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -137,7 +137,7 @@ public class JadxArgs { public boolean isDebugInfo() { return debugInfo; } - + public void setDebugInfo(boolean debugInfo) { this.debugInfo = debugInfo; } @@ -257,5 +257,4 @@ public class JadxArgs { sb.append('}'); return sb.toString(); } - } diff --git a/jadx-core/src/test/java/jadx/NotYetImplemented.java b/jadx-core/src/test/java/jadx/NotYetImplemented.java index 299f0d279..4ed618821 100644 --- a/jadx-core/src/test/java/jadx/NotYetImplemented.java +++ b/jadx-core/src/test/java/jadx/NotYetImplemented.java @@ -7,14 +7,14 @@ import java.lang.annotation.Target; /** * Indicates a test which is known to fail. - * + * *

This would cause a failure to be considered as success and a success as failure, * with the benefit of updating the related issue when it has been resolved even unintentionally.

- * + * *

To have an effect, the test class must be annotated with: - * + * * - * @ExtendWith(NotYetImplementedExtension.class) + * @ExtendWith(NotYetImplementedExtension.class) * *

*/ diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestImportGenericMap.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestImportGenericMap.java index f62996683..b2e10dac1 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestImportGenericMap.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestImportGenericMap.java @@ -1,14 +1,14 @@ package jadx.tests.integration.generics; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; - import org.junit.jupiter.api.Test; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + public class TestImportGenericMap extends IntegrationTest { @Test @@ -25,16 +25,15 @@ public class TestImportGenericMap extends IntegrationTest { final class SuperClass { - interface ToImport { - } + interface ToImport { + } - interface NotToImport { - } + interface NotToImport { + } - static final class Class1 { - } + static final class Class1 { + } - public SuperClass(Class1 zzf) { - } - -} \ No newline at end of file + public SuperClass(Class1 zzf) { + } +} diff --git a/jadx-core/src/test/resources/logback.xml b/jadx-core/src/test/resources/logback.xml index a12ef37c2..3fcc38c08 100644 --- a/jadx-core/src/test/resources/logback.xml +++ b/jadx-core/src/test/resources/logback.xml @@ -1,13 +1,13 @@ - - - %d{HH:mm:ss} %-5level - %msg%n - - + + + %d{HH:mm:ss} %-5level - %msg%n + + - - - + + + diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index ef710d18b..679e3c854 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -1,11 +1,9 @@ package jadx.gui.treemodel; +import javax.swing.*; import java.util.ArrayList; import java.util.List; -import javax.swing.Icon; -import javax.swing.ImageIcon; - import org.jetbrains.annotations.NotNull; import jadx.api.JavaClass; @@ -47,7 +45,7 @@ public class JPackage extends JNode implements Comparable { List excludedPackages = wrapper.getExcludedPackages(); this.enabled = excludedPackages.isEmpty() || excludedPackages.stream().filter(p -> !p.isEmpty()) - .noneMatch(p -> name.equals(p) || name.startsWith(p + '.')); + .noneMatch(p -> name.equals(p) || name.startsWith(p + '.')); } public final void update() { @@ -130,7 +128,7 @@ public class JPackage extends JNode implements Comparable { public String makeLongString() { return name; } - + public boolean isEnabled() { return enabled; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 608e50c66..8b0f9e70d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1,13 +1,18 @@ package jadx.gui.ui; -import static javax.swing.KeyStroke.getKeyStroke; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.DisplayMode; -import java.awt.Font; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; +import javax.swing.*; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeWillExpandListener; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; @@ -22,38 +27,6 @@ import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.Box; -import javax.swing.ImageIcon; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JToggleButton; -import javax.swing.JToolBar; -import javax.swing.JTree; -import javax.swing.ProgressMonitor; -import javax.swing.SwingUtilities; -import javax.swing.WindowConstants; -import javax.swing.event.MenuEvent; -import javax.swing.event.MenuListener; -import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeWillExpandListener; -import javax.swing.filechooser.FileNameExtensionFilter; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; -import javax.swing.tree.TreeSelectionModel; - import org.fife.ui.rsyntaxtextarea.Theme; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,6 +56,8 @@ import jadx.gui.utils.Link; import jadx.gui.utils.NLS; import jadx.gui.utils.Utils; +import static javax.swing.KeyStroke.getKeyStroke; + @SuppressWarnings("serial") public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); @@ -343,10 +318,10 @@ public class MainWindow extends JFrame { Object obj = tree.getLastSelectedPathComponent(); if (obj instanceof JPackage) { JPackagePopUp menu = new JPackagePopUp((JPackage) obj); - menu.show(e.getComponent(), e.getX(), e.getY()); + menu.show(e.getComponent(), e.getX(), e.getY()); } } - + private void syncWithEditor() { ContentPanel selectedContentPanel = tabbedPane.getSelectedCodePanel(); if (selectedContentPanel == null) { @@ -608,8 +583,7 @@ public class MainWindow extends JFrame { public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { treeRightClickAction(e); - } - else { + } else { treeClickAction(); } } @@ -780,7 +754,7 @@ public class MainWindow extends JFrame { public void menuCanceled(MenuEvent e) { } } - + private class JPackagePopUp extends JPopupMenu { JMenuItem excludeItem = new JCheckBoxMenuItem("Exclude"); @@ -791,8 +765,7 @@ public class MainWindow extends JFrame { String fullName = pkg.getFullName(); if (excludeItem.isSelected()) { wrapper.addExcludedPackage(fullName); - } - else { + } else { wrapper.removeExcludedPackage(fullName); } reOpenFile(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java index 468e890b9..1f4334032 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java @@ -1,226 +1,226 @@ -package jadx.gui.ui.codearea; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.CompoundBorder; -import javax.swing.border.EmptyBorder; -import javax.swing.border.MatteBorder; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.Element; -import javax.swing.text.StyleConstants; -import javax.swing.text.Utilities; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.HashMap; -import java.util.Map; - -import org.fife.ui.rsyntaxtextarea.SyntaxScheme; -import org.fife.ui.rsyntaxtextarea.Token; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class LineNumbers extends JPanel implements CaretListener { - private static final Logger LOG = LoggerFactory.getLogger(LineNumbers.class); - - private static final long serialVersionUID = -4978268673635308190L; - - private static final int NUM_HEIGHT = Integer.MAX_VALUE - 1000000; - private static final Map DESKTOP_HINTS = (Map) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints"); - - private final CodeArea codeArea; - private boolean useSourceLines = true; - - private int lastDigits; - private int lastLine; - private Map fonts; - - private final transient Color numberColor; - private final transient Color currentColor; - private final transient Border border; - - public LineNumbers(CodeArea component) { - this.codeArea = component; - setFont(component.getFont()); - SyntaxScheme syntaxScheme = codeArea.getSyntaxScheme(); - numberColor = syntaxScheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground; - currentColor = syntaxScheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground; - border = new MatteBorder(0, 0, 0, 1, syntaxScheme.getStyle(Token.COMMENT_MULTILINE).foreground); - setBackground(codeArea.getBackground()); - setForeground(numberColor); - - setBorderGap(5); - setPreferredWidth(); - - component.addCaretListener(this); - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - useSourceLines = !useSourceLines; - repaint(); - } - } - }); - } - - public void setBorderGap(int borderGap) { - Border inner = new EmptyBorder(0, borderGap, 0, borderGap); - setBorder(new CompoundBorder(border, inner)); - lastDigits = 0; - } - - private void setPreferredWidth() { - Element root = codeArea.getDocument().getDefaultRootElement(); - int lines = root.getElementCount(); - int digits = Math.max(String.valueOf(lines).length(), 3); - if (lastDigits != digits) { - lastDigits = digits; - FontMetrics fontMetrics = getFontMetrics(getFont()); - int width = fontMetrics.charWidth('0') * digits; - Insets insets = getInsets(); - int preferredWidth = insets.left + insets.right + width; - - Dimension d = getPreferredSize(); - if (d != null) { - d.setSize(preferredWidth, NUM_HEIGHT); - setPreferredSize(d); - setSize(d); - } - } - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - applyRenderHints(g); - - Font font = codeArea.getFont(); - font = font.deriveFont(font.getSize2D() - 1.0f); - g.setFont(font); - - Dimension size = getSize(); - g.setColor(codeArea.getBackground()); - g.fillRect(0, 0, size.width, size.height); - - FontMetrics fontMetrics = codeArea.getFontMetrics(font); - Insets insets = getInsets(); - int availableWidth = size.width - insets.left - insets.right; - Rectangle clip = g.getClipBounds(); - int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y)); - int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height)); - - while (rowStartOffset <= endOffset) { - try { - String lineNumber = getTextLineNumber(rowStartOffset); - if (lineNumber != null) { - if (isCurrentLine(rowStartOffset)) { - g.setColor(currentColor); - } else { - g.setColor(numberColor); - } - int stringWidth = fontMetrics.stringWidth(lineNumber); - int x = availableWidth - stringWidth + insets.left; - int y = getOffsetY(rowStartOffset, fontMetrics); - g.drawString(lineNumber, x, y); - } - rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1; - } catch (Exception e) { - if (LOG.isDebugEnabled()) { - LOG.debug("Line numbers draw error", e); - } - break; - } - } - } - - private void applyRenderHints(Graphics g) { - if (g instanceof Graphics2D) { - Graphics2D g2d = (Graphics2D) g; - if (DESKTOP_HINTS != null) { - g2d.setRenderingHints(DESKTOP_HINTS); - } else { - g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - } - } - } - - private boolean isCurrentLine(int rowStartOffset) { - int caretPosition = codeArea.getCaretPosition(); - Element root = codeArea.getDocument().getDefaultRootElement(); - return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition); - } - - @Nullable - protected String getTextLineNumber(int rowStartOffset) { - Element root = codeArea.getDocument().getDefaultRootElement(); - int index = root.getElementIndex(rowStartOffset); - Element line = root.getElement(index); - if (line.getStartOffset() != rowStartOffset) { - return null; - } - int lineNumber = index + 1; - if (useSourceLines) { - Integer sourceLine = codeArea.getSourceLine(lineNumber); - if (sourceLine == null) { - return null; - } - return String.valueOf(sourceLine); - } - return String.valueOf(lineNumber); - } - - private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException { - Rectangle r = codeArea.modelToView(rowStartOffset); - if (r == null) { - throw new BadLocationException("Can't get Y offset", rowStartOffset); - } - int lineHeight = fontMetrics.getHeight(); - int y = r.y + r.height; - int descent = 0; - if (r.height == lineHeight) { - descent = fontMetrics.getDescent(); - } else { - if (fonts == null) { - fonts = new HashMap<>(); - } - Element root = codeArea.getDocument().getDefaultRootElement(); - int index = root.getElementIndex(rowStartOffset); - Element line = root.getElement(index); - for (int i = 0; i < line.getElementCount(); i++) { - Element child = line.getElement(i); - AttributeSet as = child.getAttributes(); - String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily); - Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize); - String key = fontFamily + fontSize; - FontMetrics fm = fonts.computeIfAbsent(key, k -> { - Font font = new Font(fontFamily, Font.PLAIN, fontSize); - return codeArea.getFontMetrics(font); - }); - descent = Math.max(descent, fm.getDescent()); - } - } - return y - descent; - } - - @Override - public void caretUpdate(CaretEvent e) { - int caretPosition = codeArea.getCaretPosition(); - Element root = codeArea.getDocument().getDefaultRootElement(); - int currentLine = root.getElementIndex(caretPosition); - if (lastLine != currentLine) { - repaint(); - lastLine = currentLine; - } - } - - public void setUseSourceLines(boolean useSourceLines) { - this.useSourceLines = useSourceLines; - } -} +package jadx.gui.ui.codearea; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.StyleConstants; +import javax.swing.text.Utilities; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; + +import org.fife.ui.rsyntaxtextarea.SyntaxScheme; +import org.fife.ui.rsyntaxtextarea.Token; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LineNumbers extends JPanel implements CaretListener { + private static final Logger LOG = LoggerFactory.getLogger(LineNumbers.class); + + private static final long serialVersionUID = -4978268673635308190L; + + private static final int NUM_HEIGHT = Integer.MAX_VALUE - 1000000; + private static final Map DESKTOP_HINTS = (Map) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints"); + + private final CodeArea codeArea; + private boolean useSourceLines = true; + + private int lastDigits; + private int lastLine; + private Map fonts; + + private final transient Color numberColor; + private final transient Color currentColor; + private final transient Border border; + + public LineNumbers(CodeArea component) { + this.codeArea = component; + setFont(component.getFont()); + SyntaxScheme syntaxScheme = codeArea.getSyntaxScheme(); + numberColor = syntaxScheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground; + currentColor = syntaxScheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground; + border = new MatteBorder(0, 0, 0, 1, syntaxScheme.getStyle(Token.COMMENT_MULTILINE).foreground); + setBackground(codeArea.getBackground()); + setForeground(numberColor); + + setBorderGap(5); + setPreferredWidth(); + + component.addCaretListener(this); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + useSourceLines = !useSourceLines; + repaint(); + } + } + }); + } + + public void setBorderGap(int borderGap) { + Border inner = new EmptyBorder(0, borderGap, 0, borderGap); + setBorder(new CompoundBorder(border, inner)); + lastDigits = 0; + } + + private void setPreferredWidth() { + Element root = codeArea.getDocument().getDefaultRootElement(); + int lines = root.getElementCount(); + int digits = Math.max(String.valueOf(lines).length(), 3); + if (lastDigits != digits) { + lastDigits = digits; + FontMetrics fontMetrics = getFontMetrics(getFont()); + int width = fontMetrics.charWidth('0') * digits; + Insets insets = getInsets(); + int preferredWidth = insets.left + insets.right + width; + + Dimension d = getPreferredSize(); + if (d != null) { + d.setSize(preferredWidth, NUM_HEIGHT); + setPreferredSize(d); + setSize(d); + } + } + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + applyRenderHints(g); + + Font font = codeArea.getFont(); + font = font.deriveFont(font.getSize2D() - 1.0f); + g.setFont(font); + + Dimension size = getSize(); + g.setColor(codeArea.getBackground()); + g.fillRect(0, 0, size.width, size.height); + + FontMetrics fontMetrics = codeArea.getFontMetrics(font); + Insets insets = getInsets(); + int availableWidth = size.width - insets.left - insets.right; + Rectangle clip = g.getClipBounds(); + int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y)); + int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height)); + + while (rowStartOffset <= endOffset) { + try { + String lineNumber = getTextLineNumber(rowStartOffset); + if (lineNumber != null) { + if (isCurrentLine(rowStartOffset)) { + g.setColor(currentColor); + } else { + g.setColor(numberColor); + } + int stringWidth = fontMetrics.stringWidth(lineNumber); + int x = availableWidth - stringWidth + insets.left; + int y = getOffsetY(rowStartOffset, fontMetrics); + g.drawString(lineNumber, x, y); + } + rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1; + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Line numbers draw error", e); + } + break; + } + } + } + + private void applyRenderHints(Graphics g) { + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + if (DESKTOP_HINTS != null) { + g2d.setRenderingHints(DESKTOP_HINTS); + } else { + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + } + } + } + + private boolean isCurrentLine(int rowStartOffset) { + int caretPosition = codeArea.getCaretPosition(); + Element root = codeArea.getDocument().getDefaultRootElement(); + return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition); + } + + @Nullable + protected String getTextLineNumber(int rowStartOffset) { + Element root = codeArea.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + if (line.getStartOffset() != rowStartOffset) { + return null; + } + int lineNumber = index + 1; + if (useSourceLines) { + Integer sourceLine = codeArea.getSourceLine(lineNumber); + if (sourceLine == null) { + return null; + } + return String.valueOf(sourceLine); + } + return String.valueOf(lineNumber); + } + + private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException { + Rectangle r = codeArea.modelToView(rowStartOffset); + if (r == null) { + throw new BadLocationException("Can't get Y offset", rowStartOffset); + } + int lineHeight = fontMetrics.getHeight(); + int y = r.y + r.height; + int descent = 0; + if (r.height == lineHeight) { + descent = fontMetrics.getDescent(); + } else { + if (fonts == null) { + fonts = new HashMap<>(); + } + Element root = codeArea.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + for (int i = 0; i < line.getElementCount(); i++) { + Element child = line.getElement(i); + AttributeSet as = child.getAttributes(); + String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily); + Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize); + String key = fontFamily + fontSize; + FontMetrics fm = fonts.computeIfAbsent(key, k -> { + Font font = new Font(fontFamily, Font.PLAIN, fontSize); + return codeArea.getFontMetrics(font); + }); + descent = Math.max(descent, fm.getDescent()); + } + } + return y - descent; + } + + @Override + public void caretUpdate(CaretEvent e) { + int caretPosition = codeArea.getCaretPosition(); + Element root = codeArea.getDocument().getDefaultRootElement(); + int currentLine = root.getElementIndex(caretPosition); + if (lastLine != currentLine) { + repaint(); + lastLine = currentLine; + } + } + + public void setUseSourceLines(boolean useSourceLines) { + this.useSourceLines = useSourceLines; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/NLS.java b/jadx-gui/src/main/java/jadx/gui/utils/NLS.java index 74ee8594e..a835c3d8a 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/NLS.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/NLS.java @@ -1,108 +1,108 @@ -package jadx.gui.utils; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; -import java.util.Vector; - -import jadx.core.utils.exceptions.JadxRuntimeException; - -public class NLS { - - private static final Vector i18nLocales = new Vector<>(); - - private static final Map i18nMessagesMap = new HashMap<>(); - - private static final ResourceBundle fallbackMessagesMap; - private static final LangLocale localLocale; - - // Use these two fields to avoid invoking Map.get() method twice. - private static ResourceBundle localizedMessagesMap; - private static LangLocale currentLocale; - - static { - localLocale = new LangLocale(Locale.getDefault()); - - i18nLocales.add(new LangLocale("en", "US")); // As default language - i18nLocales.add(new LangLocale("zh", "CN")); - i18nLocales.add(new LangLocale("es", "ES")); - - i18nLocales.forEach(NLS::load); - - LangLocale defLang = i18nLocales.get(0); - fallbackMessagesMap = i18nMessagesMap.get(defLang); - localizedMessagesMap = i18nMessagesMap.get(defLang); - } - - private NLS() { - } - - private static void load(LangLocale locale) { - ResourceBundle bundle; - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - String resName = String.format("i18n/Messages_%s.properties", locale.get()); - URL bundleUrl = classLoader.getResource(resName); - if (bundleUrl == null) { - throw new JadxRuntimeException("Locale resource not found: " + resName); - } - try (Reader reader = new InputStreamReader(bundleUrl.openStream(), StandardCharsets.UTF_8)) { - bundle = new PropertyResourceBundle(reader); - } catch (IOException e) { - throw new JadxRuntimeException("Failed to load " + resName, e); - } - i18nMessagesMap.put(locale, bundle); - } - - public static String str(String key) { - try { - return localizedMessagesMap.getString(key); - } catch (MissingResourceException e) { - return fallbackMessagesMap.getString(key); // definitely exists - } - } - - public static String str(String key, LangLocale locale) { - ResourceBundle bundle = i18nMessagesMap.get(locale); - if (bundle != null) { - try { - return bundle.getString(key); - } catch (MissingResourceException ignored) { - // use fallback string - } - } - return fallbackMessagesMap.getString(key); // definitely exists - } - - public static void setLocale(LangLocale locale) { - if (i18nMessagesMap.containsKey(locale)) { - currentLocale = locale; - } else { - currentLocale = i18nLocales.get(0); - } - localizedMessagesMap = i18nMessagesMap.get(currentLocale); - } - - public static Vector getI18nLocales() { - return i18nLocales; - } - - public static LangLocale currentLocale() { - return currentLocale; - } - - public static LangLocale defaultLocale() { - if (i18nMessagesMap.containsKey(localLocale)) { - return localLocale; - } - // fallback to english if unsupported - return i18nLocales.get(0); - } -} +package jadx.gui.utils; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.Vector; + +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class NLS { + + private static final Vector i18nLocales = new Vector<>(); + + private static final Map i18nMessagesMap = new HashMap<>(); + + private static final ResourceBundle fallbackMessagesMap; + private static final LangLocale localLocale; + + // Use these two fields to avoid invoking Map.get() method twice. + private static ResourceBundle localizedMessagesMap; + private static LangLocale currentLocale; + + static { + localLocale = new LangLocale(Locale.getDefault()); + + i18nLocales.add(new LangLocale("en", "US")); // As default language + i18nLocales.add(new LangLocale("zh", "CN")); + i18nLocales.add(new LangLocale("es", "ES")); + + i18nLocales.forEach(NLS::load); + + LangLocale defLang = i18nLocales.get(0); + fallbackMessagesMap = i18nMessagesMap.get(defLang); + localizedMessagesMap = i18nMessagesMap.get(defLang); + } + + private NLS() { + } + + private static void load(LangLocale locale) { + ResourceBundle bundle; + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + String resName = String.format("i18n/Messages_%s.properties", locale.get()); + URL bundleUrl = classLoader.getResource(resName); + if (bundleUrl == null) { + throw new JadxRuntimeException("Locale resource not found: " + resName); + } + try (Reader reader = new InputStreamReader(bundleUrl.openStream(), StandardCharsets.UTF_8)) { + bundle = new PropertyResourceBundle(reader); + } catch (IOException e) { + throw new JadxRuntimeException("Failed to load " + resName, e); + } + i18nMessagesMap.put(locale, bundle); + } + + public static String str(String key) { + try { + return localizedMessagesMap.getString(key); + } catch (MissingResourceException e) { + return fallbackMessagesMap.getString(key); // definitely exists + } + } + + public static String str(String key, LangLocale locale) { + ResourceBundle bundle = i18nMessagesMap.get(locale); + if (bundle != null) { + try { + return bundle.getString(key); + } catch (MissingResourceException ignored) { + // use fallback string + } + } + return fallbackMessagesMap.getString(key); // definitely exists + } + + public static void setLocale(LangLocale locale) { + if (i18nMessagesMap.containsKey(locale)) { + currentLocale = locale; + } else { + currentLocale = i18nLocales.get(0); + } + localizedMessagesMap = i18nMessagesMap.get(currentLocale); + } + + public static Vector getI18nLocales() { + return i18nLocales; + } + + public static LangLocale currentLocale() { + return currentLocale; + } + + public static LangLocale defaultLocale() { + if (i18nMessagesMap.containsKey(localLocale)) { + return localLocale; + } + // fallback to english if unsupported + return i18nLocales.get(0); + } +} diff --git a/jadx-samples/build.gradle b/jadx-samples/build.gradle index 9c0c88cb3..e21f7e857 100644 --- a/jadx-samples/build.gradle +++ b/jadx-samples/build.gradle @@ -1,52 +1,52 @@ project.ext { - mainSamplesClass = "jadx.samples.RunTests" - samplesJadxSrcDir = "${buildDir}/samples-jadx/src" - samplesJadxOutDir = "${buildDir}/samples-jadx/output" + mainSamplesClass = "jadx.samples.RunTests" + samplesJadxSrcDir = "${buildDir}/samples-jadx/src" + samplesJadxOutDir = "${buildDir}/samples-jadx/output" } dependencies { - compile(project(":jadx-core")) - compile(project(":jadx-cli")) + compile(project(":jadx-core")) + compile(project(":jadx-cli")) } compileJava { - options.compilerArgs << '-g:none' + options.compilerArgs << '-g:none' } -task samplesRun(type: JavaExec, dependsOn: compileJava) { - classpath = sourceSets.main.output - main = mainSamplesClass +task samplesRun(type: JavaExec, dependsOn: compileJava) { + classpath = sourceSets.main.output + main = mainSamplesClass } task samplesJar(type: Jar, dependsOn: samplesRun) { - baseName = 'samples' - from sourceSets.main.output + baseName = 'samples' + from sourceSets.main.output } -task samplesJadxCreate(type: JavaExec, dependsOn: samplesJar) { - classpath = sourceSets.main.output + configurations.compile - main = project(":jadx-cli").mainClassName - args = ['-d', samplesJadxSrcDir, samplesJar.archivePath] +task samplesJadxCreate(type: JavaExec, dependsOn: samplesJar) { + classpath = sourceSets.main.output + configurations.compile + main = project(":jadx-cli").mainClassName + args = ['-d', samplesJadxSrcDir, samplesJar.archivePath] } -task samplesJadxCompile(type: JavaCompile, dependsOn: samplesJadxCreate) { - classpath = configurations.compile - destinationDir = file samplesJadxOutDir - source = samplesJadxSrcDir - options.encoding = "UTF-8" +task samplesJadxCompile(type: JavaCompile, dependsOn: samplesJadxCreate) { + classpath = configurations.compile + destinationDir = file samplesJadxOutDir + source = samplesJadxSrcDir + options.encoding = "UTF-8" } -task samplesJadxRun(type: JavaExec, dependsOn: samplesJadxCompile) { - classpath = files samplesJadxOutDir - main = mainSamplesClass +task samplesJadxRun(type: JavaExec, dependsOn: samplesJadxCompile) { + classpath = files samplesJadxOutDir + main = mainSamplesClass } task samples(dependsOn: samplesJadxRun) { } task cleanGeneratedFiles(type: Delete) { - delete samplesJadxSrcDir - delete samplesJadxOutDir + delete samplesJadxSrcDir + delete samplesJadxOutDir } clean.dependsOn cleanGeneratedFiles diff --git a/jadx-samples/src/main/java/jadx/samples/TestInitializers.java b/jadx-samples/src/main/java/jadx/samples/TestInitializers.java index 660814c00..8f195ad63 100644 --- a/jadx-samples/src/main/java/jadx/samples/TestInitializers.java +++ b/jadx-samples/src/main/java/jadx/samples/TestInitializers.java @@ -5,14 +5,14 @@ public class TestInitializers extends AbstractTest { private static String a; private static int counter; private A c_a; - + public static class A { public static String a; - + static { a = "a1"; } - + public boolean z() { return true; } @@ -29,11 +29,11 @@ public class TestInitializers extends AbstractTest { b = 1; } } - + public B(int _b) { b = _b; } - + public void setB(int _b) { b = _b; } @@ -41,11 +41,11 @@ public class TestInitializers extends AbstractTest { public int getB() { return b; } - + public int getBBB() { return bbb; } - + { bbb = 123; } @@ -55,7 +55,7 @@ public class TestInitializers extends AbstractTest { a = "a0"; counter = 0; } - + { c_a = new A(); } @@ -90,15 +90,14 @@ public class TestInitializers extends AbstractTest { assertTrue((new B()).getB() == -1); assertTrue(counter == 1); - + B b3 = new B(3); assertTrue((b3.getB() == 3) && (b3.getBBB() == 123)); - + return true; } public static void main(String[] args) throws Exception { new TestInitializers().testRun(); } - } diff --git a/jadx-samples/src/main/java/jadx/samples/TestInner3.java b/jadx-samples/src/main/java/jadx/samples/TestInner3.java index 0ffa7dcda..cb64860cc 100644 --- a/jadx-samples/src/main/java/jadx/samples/TestInner3.java +++ b/jadx-samples/src/main/java/jadx/samples/TestInner3.java @@ -5,11 +5,11 @@ public class TestInner3 extends AbstractTest { private String i0; public class A { - + protected String a; - + public A() { - a=""; + a = ""; } public String a() { @@ -39,7 +39,7 @@ public class TestInner3 extends AbstractTest { public String i() { String result = TestInner3.this.i0 + I0.this.i0 + I0.this.i1 + i0 + i1 + i2; - + A a = new A() { public String a() { @@ -82,5 +82,4 @@ public class TestInner3 extends AbstractTest { public static void main(String[] args) throws Exception { new TestInner2().testRun(); } - }