From fbc5b14d66b37b88bf1dc18386b530e874550240 Mon Sep 17 00:00:00 2001 From: emmanue1 Date: Sat, 1 Jun 2019 18:40:10 +0200 Subject: [PATCH] Add Java 9+ module support --- .../treenode/TreeNodeFactoryService.java | 2 +- .../JmodContainerFactoryProvider.java | 2 +- .../AbstractFileLoaderProvider.java | 3 +- .../indexer/JavaFileIndexerProvider.java | 101 ++++++----- .../ClassFileTreeNodeFactoryProvider.java | 10 ++ .../JavaFileTreeNodeFactoryProvider.java | 4 +- ...ModuleInfoFileTreeNodeFactoryProvider.java | 66 ++++++++ .../parser/antlr/AbstractJavaListener.java | 4 +- .../gui/view/component/AbstractTextPage.java | 22 ++- .../jd/gui/view/component/ClassFilePage.java | 6 +- .../view/component/CustomLineNumbersPage.java | 39 +++-- .../jd/gui/view/component/JavaFilePage.java | 8 +- .../view/component/ModuleInfoFilePage.java | 160 ++++++++++++++++++ .../services/org.jd.gui.spi.TreeNodeFactory | 1 + 14 files changed, 347 insertions(+), 81 deletions(-) create mode 100644 services/src/main/java/org/jd/gui/service/treenode/ModuleInfoFileTreeNodeFactoryProvider.java create mode 100644 services/src/main/java/org/jd/gui/view/component/ModuleInfoFilePage.java diff --git a/app/src/main/java/org/jd/gui/service/treenode/TreeNodeFactoryService.java b/app/src/main/java/org/jd/gui/service/treenode/TreeNodeFactoryService.java index 49c3514d..e7fe27ce 100644 --- a/app/src/main/java/org/jd/gui/service/treenode/TreeNodeFactoryService.java +++ b/app/src/main/java/org/jd/gui/service/treenode/TreeNodeFactoryService.java @@ -43,7 +43,7 @@ public TreeNodeFactory get(Container.Entry entry) { } protected TreeNodeFactory get(String containerType, Container.Entry entry) { - String path = entry.getPath();; + String path = entry.getPath(); String type = entry.isDirectory() ? "dir" : "file"; String prefix = containerType + ':' + type + ':'; TreeNodeFactory factory = null; diff --git a/services/src/main/java/org/jd/gui/service/container/JmodContainerFactoryProvider.java b/services/src/main/java/org/jd/gui/service/container/JmodContainerFactoryProvider.java index 544a3e30..24e37de2 100644 --- a/services/src/main/java/org/jd/gui/service/container/JmodContainerFactoryProvider.java +++ b/services/src/main/java/org/jd/gui/service/container/JmodContainerFactoryProvider.java @@ -26,7 +26,7 @@ public boolean accept(API api, Path rootPath) { if (rootPath.toUri().toString().toLowerCase().endsWith(".jmod!/")) { return true; } else { - // Extension: accept uncompressed WAR file containing a folder 'WEB-INF' + // Extension: accept uncompressed JMOD file containing a folder 'classes' try { return rootPath.getFileSystem().provider().getScheme().equals("file") && Files.exists(rootPath.resolve("classes")); } catch (InvalidPathException e) { diff --git a/services/src/main/java/org/jd/gui/service/fileloader/AbstractFileLoaderProvider.java b/services/src/main/java/org/jd/gui/service/fileloader/AbstractFileLoaderProvider.java index 5c71870f..f5694c7e 100644 --- a/services/src/main/java/org/jd/gui/service/fileloader/AbstractFileLoaderProvider.java +++ b/services/src/main/java/org/jd/gui/service/fileloader/AbstractFileLoaderProvider.java @@ -44,8 +44,9 @@ protected T load(API api, File file, Path r TreeNodeFactory treeNodeFactory = api.getTreeNodeFactory(parentEntry); Object data = (treeNodeFactory != null) ? treeNodeFactory.make(api, parentEntry).getUserObject() : null; Icon icon = (data instanceof TreeNodeData) ? ((TreeNodeData)data).getIcon() : null; + String location = file.getPath(); - api.addPanel(file.getName(), icon, "Location: " + file.getAbsolutePath(), mainPanel); + api.addPanel(file.getName(), icon, "Location: " + location, mainPanel); return mainPanel; } } diff --git a/services/src/main/java/org/jd/gui/service/indexer/JavaFileIndexerProvider.java b/services/src/main/java/org/jd/gui/service/indexer/JavaFileIndexerProvider.java index 0c70c4b8..a7acd32f 100644 --- a/services/src/main/java/org/jd/gui/service/indexer/JavaFileIndexerProvider.java +++ b/services/src/main/java/org/jd/gui/service/indexer/JavaFileIndexerProvider.java @@ -122,43 +122,47 @@ public void enterPackageDeclaration(JavaParser.PackageDeclarationContext ctx) { protected void enterTypeDeclaration(ParserRuleContext ctx) { // Add type declaration - String typeName = ctx.getToken(JavaParser.Identifier, 0).getText(); - int length = sbTypeDeclaration.length(); + TerminalNode identifier = ctx.getToken(JavaParser.Identifier, 0); - if ((length == 0) || (sbTypeDeclaration.charAt(length-1) == '/')) { - sbTypeDeclaration.append(typeName); - } else { - sbTypeDeclaration.append('$').append(typeName); - } + if (identifier != null) { + String typeName = identifier.getText(); + int length = sbTypeDeclaration.length(); - String internalTypeName = sbTypeDeclaration.toString(); - typeDeclarationSet.add(internalTypeName); - nameToInternalTypeName.put(typeName, internalTypeName); + if ((length == 0) || (sbTypeDeclaration.charAt(length - 1) == '/')) { + sbTypeDeclaration.append(typeName); + } else { + sbTypeDeclaration.append('$').append(typeName); + } - HashSet superInternalTypeNameSet = new HashSet<>(); + String internalTypeName = sbTypeDeclaration.toString(); + typeDeclarationSet.add(internalTypeName); + nameToInternalTypeName.put(typeName, internalTypeName); - // Add super type reference - JavaParser.TypeContext superType = ctx.getRuleContext(JavaParser.TypeContext.class, 0); - if (superType != null) { - String superQualifiedTypeName = resolveInternalTypeName(superType.classOrInterfaceType().Identifier()); + HashSet superInternalTypeNameSet = new HashSet<>(); - if (superQualifiedTypeName.charAt(0) != '*') - superInternalTypeNameSet.add(superQualifiedTypeName); - } + // Add super type reference + JavaParser.TypeContext superType = ctx.getRuleContext(JavaParser.TypeContext.class, 0); + if (superType != null) { + String superQualifiedTypeName = resolveInternalTypeName(superType.classOrInterfaceType().Identifier()); + + if (superQualifiedTypeName.charAt(0) != '*') + superInternalTypeNameSet.add(superQualifiedTypeName); + } - // Add implementation references - JavaParser.TypeListContext superInterfaces = ctx.getRuleContext(JavaParser.TypeListContext.class, 0); - if (superInterfaces != null) { - for (JavaParser.TypeContext superInterface : superInterfaces.type()) { - String superQualifiedInterfaceName = resolveInternalTypeName(superInterface.classOrInterfaceType().Identifier()); + // Add implementation references + JavaParser.TypeListContext superInterfaces = ctx.getRuleContext(JavaParser.TypeListContext.class, 0); + if (superInterfaces != null) { + for (JavaParser.TypeContext superInterface : superInterfaces.type()) { + String superQualifiedInterfaceName = resolveInternalTypeName(superInterface.classOrInterfaceType().Identifier()); - if (superQualifiedInterfaceName.charAt(0) != '*') - superInternalTypeNameSet.add(superQualifiedInterfaceName); + if (superQualifiedInterfaceName.charAt(0) != '*') + superInternalTypeNameSet.add(superQualifiedInterfaceName); + } } - } - if (! superInternalTypeNameSet.isEmpty()) { - superTypeNamesMap.put(internalTypeName, superInternalTypeNameSet); + if (!superInternalTypeNameSet.isEmpty()) { + superTypeNamesMap.put(internalTypeName, superInternalTypeNameSet); + } } } @@ -197,19 +201,31 @@ public void enterConstDeclaration(JavaParser.ConstDeclarationContext ctx) { public void enterFieldDeclaration(JavaParser.FieldDeclarationContext ctx) { for (JavaParser.VariableDeclaratorContext declaration : ctx.variableDeclarators().variableDeclarator()) { - String name = declaration.variableDeclaratorId().Identifier().getText(); - fieldDeclarationSet.add(name); + TerminalNode identifier = declaration.variableDeclaratorId().Identifier(); + + if (identifier != null) { + String name = identifier.getText(); + fieldDeclarationSet.add(name); + } } } public void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx) { - String name = ctx.Identifier().getText(); - methodDeclarationSet.add(name); + TerminalNode identifier = ctx.Identifier(); + + if (identifier != null) { + String name = identifier.getText(); + methodDeclarationSet.add(name); + } } public void enterInterfaceMethodDeclaration(JavaParser.InterfaceMethodDeclarationContext ctx) { - String name = ctx.Identifier().getText(); - methodDeclarationSet.add(name); + TerminalNode identifier = ctx.Identifier(); + + if (identifier != null) { + String name = identifier.getText(); + methodDeclarationSet.add(name); + } } public void enterConstructorDeclaration(JavaParser.ConstructorDeclarationContext ctx) { @@ -282,15 +298,18 @@ protected TerminalNode getToken(List children, int i, int type) { protected TerminalNode getRightTerminalNode(ParseTree pt) { if (pt instanceof ParserRuleContext) { List children = ((ParserRuleContext)pt).children; - int size = children.size(); - if (size > 0) { - ParseTree last = children.get(size - 1); + if (children != null) { + int size = children.size(); - if (last instanceof TerminalNode) { - return (TerminalNode) last; - } else { - return getRightTerminalNode(last); + if (size > 0) { + ParseTree last = children.get(size - 1); + + if (last instanceof TerminalNode) { + return (TerminalNode) last; + } else { + return getRightTerminalNode(last); + } } } } diff --git a/services/src/main/java/org/jd/gui/service/treenode/ClassFileTreeNodeFactoryProvider.java b/services/src/main/java/org/jd/gui/service/treenode/ClassFileTreeNodeFactoryProvider.java index a64a8532..a1ae4684 100644 --- a/services/src/main/java/org/jd/gui/service/treenode/ClassFileTreeNodeFactoryProvider.java +++ b/services/src/main/java/org/jd/gui/service/treenode/ClassFileTreeNodeFactoryProvider.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.regex.Pattern; public class ClassFileTreeNodeFactoryProvider extends AbstractTypeFileTreeNodeFactoryProvider { protected static final ImageIcon CLASS_FILE_ICON = new ImageIcon(ClassFileTreeNodeFactoryProvider.class.getClassLoader().getResource("org/jd/gui/images/classf_obj.png")); @@ -37,6 +38,15 @@ public class ClassFileTreeNodeFactoryProvider extends AbstractTypeFileTreeNodeFa @Override public String[] getSelectors() { return appendSelectors("*:file:*.class"); } + @Override + public Pattern getPathPattern() { + if (externalPathPattern == null) { + return Pattern.compile("^((?!module-info\\.class).)*$"); + } else { + return externalPathPattern; + } + } + @Override @SuppressWarnings("unchecked") public T make(API api, Container.Entry entry) { diff --git a/services/src/main/java/org/jd/gui/service/treenode/JavaFileTreeNodeFactoryProvider.java b/services/src/main/java/org/jd/gui/service/treenode/JavaFileTreeNodeFactoryProvider.java index 42d8f2aa..cd733e7e 100644 --- a/services/src/main/java/org/jd/gui/service/treenode/JavaFileTreeNodeFactoryProvider.java +++ b/services/src/main/java/org/jd/gui/service/treenode/JavaFileTreeNodeFactoryProvider.java @@ -43,10 +43,10 @@ public T makePage(API a, Container.Entry e) @Override public String makeTip(API api, Container.Entry entry) { - File file = new File(entry.getContainer().getRoot().getUri()); + String location = new File(entry.getUri()).getPath(); StringBuilder tip = new StringBuilder("Location: "); - tip.append(file.getPath()); + tip.append(location); tip.append(""); return tip.toString(); diff --git a/services/src/main/java/org/jd/gui/service/treenode/ModuleInfoFileTreeNodeFactoryProvider.java b/services/src/main/java/org/jd/gui/service/treenode/ModuleInfoFileTreeNodeFactoryProvider.java new file mode 100644 index 00000000..a0ea3707 --- /dev/null +++ b/services/src/main/java/org/jd/gui/service/treenode/ModuleInfoFileTreeNodeFactoryProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008-2019 Emmanuel Dupuy. + * This project is distributed under the GPLv3 license. + * This is a Copyleft license that gives the user the right to use, + * copy and modify the code freely for non-commercial purposes. + */ + +package org.jd.gui.service.treenode; + +import org.jd.gui.api.API; +import org.jd.gui.api.feature.ContainerEntryGettable; +import org.jd.gui.api.feature.UriGettable; +import org.jd.gui.api.model.Container; +import org.jd.gui.util.exception.ExceptionUtil; +import org.jd.gui.view.component.ModuleInfoFilePage; +import org.jd.gui.view.data.TreeNodeBean; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import java.io.File; +import java.util.regex.Pattern; + +public class ModuleInfoFileTreeNodeFactoryProvider extends ClassFileTreeNodeFactoryProvider { + protected static final Factory FACTORY = new Factory(); + + static { + // Early class loading + try { + Class.forName(ModuleInfoFilePage.class.getName()); + } catch (Exception e) { + assert ExceptionUtil.printStackTrace(e); + } + } + + @Override public String[] getSelectors() { return appendSelectors("*:file:*/module-info.class"); } + + @Override public Pattern getPathPattern() { return externalPathPattern; } + + @Override + @SuppressWarnings("unchecked") + public T make(API api, Container.Entry entry) { + int lastSlashIndex = entry.getPath().lastIndexOf('/'); + String label = entry.getPath().substring(lastSlashIndex+1); + return (T)new FileTreeNode(entry, new TreeNodeBean(label, CLASS_FILE_ICON), FACTORY); + } + + protected static class Factory implements AbstractTypeFileTreeNodeFactoryProvider.PageAndTipFactory { + // --- PageAndTipFactory --- // + @Override + @SuppressWarnings("unchecked") + public T makePage(API a, Container.Entry e) { + return (T)new ModuleInfoFilePage(a, e); + } + + @Override + public String makeTip(API api, Container.Entry entry) { + String location = new File(entry.getUri()).getPath(); + StringBuilder tip = new StringBuilder("Location: "); + + tip.append(location); + tip.append(""); + + return tip.toString(); + } + } +} diff --git a/services/src/main/java/org/jd/gui/util/parser/antlr/AbstractJavaListener.java b/services/src/main/java/org/jd/gui/util/parser/antlr/AbstractJavaListener.java index cf34f302..ab312e4b 100644 --- a/services/src/main/java/org/jd/gui/util/parser/antlr/AbstractJavaListener.java +++ b/services/src/main/java/org/jd/gui/util/parser/antlr/AbstractJavaListener.java @@ -114,8 +114,8 @@ protected String resolveInternalTypeName(List identifiers) { typeNameCache.put(name, qualifiedName); return qualifiedName; } - } catch (ClassNotFoundException e) { - assert ExceptionUtil.printStackTrace(e); + } catch (ClassNotFoundException ignore) { + // Ignore class loading error } // Type not found diff --git a/services/src/main/java/org/jd/gui/view/component/AbstractTextPage.java b/services/src/main/java/org/jd/gui/view/component/AbstractTextPage.java index fcb79568..1609dbfb 100644 --- a/services/src/main/java/org/jd/gui/view/component/AbstractTextPage.java +++ b/services/src/main/java/org/jd/gui/view/component/AbstractTextPage.java @@ -171,23 +171,21 @@ public void setCaretPositionAndCenter(DocumentRange range) { if (!foldsExpanded) { try { - Rectangle r = textArea.modelToView(start); + Rectangle rec = textArea.modelToView(start); - if (r != null) { + if (rec != null) { // Visible - setCaretPositionAndCenter(start, end, r); + setCaretPositionAndCenter(start, end, rec); } else { // Not visible yet - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - Rectangle r = textArea.modelToView(start); - if (r != null) { - setCaretPositionAndCenter(start, end, r); - } - } catch (BadLocationException e) { - assert ExceptionUtil.printStackTrace(e); + SwingUtilities.invokeLater(() -> { + try { + Rectangle r = textArea.modelToView(start); + if (r != null) { + setCaretPositionAndCenter(start, end, r); } + } catch (BadLocationException e) { + assert ExceptionUtil.printStackTrace(e); } }); } diff --git a/services/src/main/java/org/jd/gui/view/component/ClassFilePage.java b/services/src/main/java/org/jd/gui/view/component/ClassFilePage.java index a1d06616..67cf1ed1 100644 --- a/services/src/main/java/org/jd/gui/view/component/ClassFilePage.java +++ b/services/src/main/java/org/jd/gui/view/component/ClassFilePage.java @@ -84,7 +84,6 @@ public void decompile(Map preferences) { // Decompile class file DECOMPILER.decompile(loader, printer, entryInternalName, configuration); - setText(printer.getStringBuffer().toString()); } catch (Throwable t) { assert ExceptionUtil.printStackTrace(t); setText("// INTERNAL ERROR //"); @@ -154,6 +153,11 @@ public void start(int maxLineNumber, int majorVersion, int minorVersion) { } } + @Override + public void end() { + setText(stringBuffer.toString()); + } + // --- Add strings --- // @Override public void printStringConstant(String constant, String ownerInternalName) { diff --git a/services/src/main/java/org/jd/gui/view/component/CustomLineNumbersPage.java b/services/src/main/java/org/jd/gui/view/component/CustomLineNumbersPage.java index 8e0d3f72..c02b8e9f 100644 --- a/services/src/main/java/org/jd/gui/view/component/CustomLineNumbersPage.java +++ b/services/src/main/java/org/jd/gui/view/component/CustomLineNumbersPage.java @@ -45,46 +45,57 @@ public void setShowMisalignment(boolean b) { protected int[] lineNumberMap = null; protected int maxLineNumber = 0; - public void setMaxLineNumber(int maxLineNumber) { + protected void setMaxLineNumber(int maxLineNumber) { if (maxLineNumber > 0) { if (lineNumberMap == null) { - lineNumberMap = new int[maxLineNumber * 3 / 2]; + lineNumberMap = new int[maxLineNumber+1]; } else if (lineNumberMap.length <= maxLineNumber) { - int[] tmp = new int[maxLineNumber * 3 / 2]; + int[] tmp = new int[maxLineNumber+1]; System.arraycopy(lineNumberMap, 0, tmp, 0, lineNumberMap.length); lineNumberMap = tmp; } - if (this.maxLineNumber < maxLineNumber) { - this.maxLineNumber = maxLineNumber; - } + this.maxLineNumber = maxLineNumber; } } - public void initLineNumbers(int maxLineNumber) { - setMaxLineNumber(maxLineNumber); + protected void initLineNumbers() { + String text = getText(); + int len = text.length(); + + if (len == 0) { + setMaxLineNumber(0); + } else { + int mln = len - text.replace("\n", "").length(); + + if (text.charAt(len-1) != '\n') { + mln++; + } + + setMaxLineNumber(mln); - for (int i=1; i<=maxLineNumber; i++) { - lineNumberMap[i] = i; + for (int i=1; i<=maxLineNumber; i++) { + lineNumberMap[i] = i; + } } } - public void setLineNumber(int textAreaLineNumber, int originalLineNumber) { + protected void setLineNumber(int textAreaLineNumber, int originalLineNumber) { if (originalLineNumber > 0) { setMaxLineNumber(textAreaLineNumber); lineNumberMap[textAreaLineNumber] = originalLineNumber; } } - public void clearLineNumbers() { + protected void clearLineNumbers() { if (lineNumberMap != null) { Arrays.fill(lineNumberMap, 0); } } - public int getMaximumSourceLineNumber() { return maxLineNumber; } + protected int getMaximumSourceLineNumber() { return maxLineNumber; } - public int getTextAreaLineNumber(int originalLineNumber) { + protected int getTextAreaLineNumber(int originalLineNumber) { int textAreaLineNumber = 1; int greatestLowerSourceLineNumber = 0; int i = lineNumberMap.length; diff --git a/services/src/main/java/org/jd/gui/view/component/JavaFilePage.java b/services/src/main/java/org/jd/gui/view/component/JavaFilePage.java index ec298399..27551ffd 100644 --- a/services/src/main/java/org/jd/gui/view/component/JavaFilePage.java +++ b/services/src/main/java/org/jd/gui/view/component/JavaFilePage.java @@ -40,12 +40,8 @@ public JavaFilePage(API api, Container.Entry entry) { referenceListener.init(declarationListener); ANTLRJavaParser.parse(new ANTLRInputStream(text), referenceListener); // Display - initLineNumbers(getMaxLineNumber(text)); setText(text); - } - - private static int getMaxLineNumber(String text) { - return text.length() - text.replace("\n", "").length(); + initLineNumbers(); } public String getSyntaxStyle() { return SyntaxConstants.SYNTAX_STYLE_JAVA; } @@ -396,7 +392,7 @@ public void enterExpression(JavaParser.ExpressionContext ctx) { } } } - } else { + } else if (ctx.primary() != null) { TerminalNode identifier = ctx.primary().Identifier(); if (identifier != null) { diff --git a/services/src/main/java/org/jd/gui/view/component/ModuleInfoFilePage.java b/services/src/main/java/org/jd/gui/view/component/ModuleInfoFilePage.java new file mode 100644 index 00000000..44333c7e --- /dev/null +++ b/services/src/main/java/org/jd/gui/view/component/ModuleInfoFilePage.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2008-2019 Emmanuel Dupuy. + * This project is distributed under the GPLv3 license. + * This is a Copyleft license that gives the user the right to use, + * copy and modify the code freely for non-commercial purposes. + */ + +package org.jd.gui.view.component; + +import org.fife.ui.rsyntaxtextarea.*; +import org.jd.gui.api.API; +import org.jd.gui.api.model.Container; +import org.jd.gui.util.decompiler.ContainerLoader; +import org.jd.gui.util.exception.ExceptionUtil; + +import javax.swing.text.Segment; +import java.util.Map; + +public class ModuleInfoFilePage extends ClassFilePage { + public static final String SYNTAX_STYLE_JAVA_MODULE = "text/java-module"; + + static { + // Add a new token maker for Java 9+ module + AbstractTokenMakerFactory atmf = (AbstractTokenMakerFactory)TokenMakerFactory.getDefaultInstance(); + atmf.putMapping(SYNTAX_STYLE_JAVA_MODULE, ModuleInfoTokenMaker.class.getName()); + } + + public ModuleInfoFilePage(API api, Container.Entry entry) { + super(api, entry); + } + + public void decompile(Map preferences) { + try { + // Clear ... + clearHyperlinks(); + clearLineNumbers(); + typeDeclarations.clear(); + + // Init preferences + boolean unicodeEscape = getPreferenceValue(preferences, ESCAPE_UNICODE_CHARACTERS, false); + + // Init loader + ContainerLoader loader = new ContainerLoader(entry); + + // Init printer + ModuleInfoFilePrinter printer = new ModuleInfoFilePrinter(); + printer.setUnicodeEscape(unicodeEscape); + + // Format internal name + String entryPath = entry.getPath(); + assert entryPath.endsWith(".class"); + String entryInternalName = entryPath.substring(0, entryPath.length() - 6); // 6 = ".class".length() + + // Decompile class file + DECOMPILER.decompile(loader, printer, entryInternalName); + } catch (Throwable t) { + assert ExceptionUtil.printStackTrace(t); + setText("// INTERNAL ERROR //"); + } + } + + public String getSyntaxStyle() { return SYNTAX_STYLE_JAVA_MODULE; } + + public class ModuleInfoFilePrinter extends ClassFilePrinter { + @Override + public void start(int maxLineNumber, int majorVersion, int minorVersion) {} + + @Override + public void end() { + setText(stringBuffer.toString()); + initLineNumbers(); + } + } + + // https://github.com/bobbylight/RSyntaxTextArea/wiki/Adding-Syntax-Highlighting-for-a-new-Language + public static class ModuleInfoTokenMaker extends AbstractTokenMaker { + @Override + public TokenMap getWordsToHighlight() { + TokenMap tokenMap = new TokenMap(); + + tokenMap.put("exports", Token.RESERVED_WORD); + tokenMap.put("module", Token.RESERVED_WORD); + tokenMap.put("open", Token.RESERVED_WORD); + tokenMap.put("opens", Token.RESERVED_WORD); + tokenMap.put("provides", Token.RESERVED_WORD); + tokenMap.put("requires", Token.RESERVED_WORD); + tokenMap.put("to", Token.RESERVED_WORD); + tokenMap.put("transitive", Token.RESERVED_WORD); + tokenMap.put("uses", Token.RESERVED_WORD); + tokenMap.put("with", Token.RESERVED_WORD); + + return tokenMap; + } + + @Override + public void addToken(Segment segment, int start, int end, int tokenType, int startOffset) { + // This assumes all keywords, etc. were parsed as "identifiers." + if (tokenType==Token.IDENTIFIER) { + int value = wordsToHighlight.get(segment, start, end); + if (value != -1) { + tokenType = value; + } + } + super.addToken(segment, start, end, tokenType, startOffset); + } + + @Override + public Token getTokenList(Segment text, int startTokenType, int startOffset) { + resetTokenList(); + + char[] array = text.array; + int offset = text.offset; + int end = offset + text.count; + + int newStartOffset = startOffset - offset; + + int currentTokenStart = offset; + int currentTokenType = startTokenType; + + for (int i=offset; i