From 5614cd608b6d25ba3b7e03a5d113b4eef79d148b Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Tue, 20 Jan 2015 21:34:40 -0500 Subject: [PATCH 01/13] Merged in the latest changes to the "javadoc" command from OakBot. --- javadocs/readme.txt | 22 +- javadocs/sample.zip | Bin 27677 -> 9141 bytes .../gmail/inverseconduit/bot/DefaultBot.java | 23 +- .../com/gmail/inverseconduit/bot/Program.java | 17 +- .../commands/CommandHandle.java | 5 +- .../inverseconduit/javadoc/ClassInfo.java | 137 +- .../javadoc/ClassInfoXmlParser.java | 179 + .../inverseconduit/javadoc/ClassName.java | 49 + .../javadoc/DescriptionNodeVisitor.java | 144 - .../javadoc/Java8PageParser.java | 81 - .../javadoc/JavaDocAccessor.java | 731 +++- .../inverseconduit/javadoc/JavadocDao.java | 223 +- .../javadoc/JavadocLibrary.java | 61 - .../javadoc/JsoupPageParser.java | 94 - .../javadoc/LibraryZipFile.java | 229 ++ .../inverseconduit/javadoc/MethodInfo.java | 181 + .../inverseconduit/javadoc/PageLoader.java | 26 - .../inverseconduit/javadoc/PageParser.java | 32 - .../inverseconduit/javadoc/ParameterInfo.java | 39 + .../inverseconduit/javadoc/ZipPageLoader.java | 80 - .../inverseconduit/utils/ChatBuilder.java | 196 + .../inverseconduit/utils/DocumentWrapper.java | 107 + .../javadoc/CommandTextParserTest.java | 123 + .../javadoc/Java8PageParserTest.java | 121 - .../javadoc/JavadocDaoTest.java | 212 +- .../javadoc/JsoupPageParserTest.java | 54 - .../javadoc/LibraryZipFileTest.java | 87 + .../javadoc/ZipPageLoaderTest.java | 82 - .../inverseconduit/javadoc/Attribute.html | 562 --- .../LibraryZipFileTest-no-attributes.zip | Bin 0 -> 17834 bytes .../javadoc/LibraryZipFileTest-no-info.zip | Bin 0 -> 17679 bytes .../javadoc/LibraryZipFileTest.zip | Bin 0 -> 18732 bytes .../gmail/inverseconduit/javadoc/String.html | 3573 ----------------- .../javadoc/StringBufferInputStream.html | 557 --- .../javadoc/SuppressWarnings.html | 254 -- .../javadoc/java8-allclasses-frame.html | 22 - .../inverseconduit/javadoc/javadoc-file.zip | Bin 38541 -> 0 bytes .../javadoc-without-allclasses-frame.zip | Bin 12340 -> 0 bytes .../javadoc/jsoup-allclasses-frame.html | 39 - 39 files changed, 2282 insertions(+), 6060 deletions(-) create mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/ClassInfoXmlParser.java create mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/DescriptionNodeVisitor.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/Java8PageParser.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/JavadocLibrary.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/JsoupPageParser.java create mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java create mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/PageLoader.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/PageParser.java create mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java delete mode 100644 src/main/java/com/gmail/inverseconduit/javadoc/ZipPageLoader.java create mode 100644 src/main/java/com/gmail/inverseconduit/utils/ChatBuilder.java create mode 100644 src/main/java/com/gmail/inverseconduit/utils/DocumentWrapper.java create mode 100644 src/test/java/com/gmail/inverseconduit/javadoc/CommandTextParserTest.java delete mode 100644 src/test/java/com/gmail/inverseconduit/javadoc/Java8PageParserTest.java delete mode 100644 src/test/java/com/gmail/inverseconduit/javadoc/JsoupPageParserTest.java create mode 100644 src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java delete mode 100644 src/test/java/com/gmail/inverseconduit/javadoc/ZipPageLoaderTest.java delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/Attribute.html create mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-attributes.zip create mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-info.zip create mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.zip delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/String.html delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/StringBufferInputStream.html delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/SuppressWarnings.html delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/java8-allclasses-frame.html delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/javadoc-file.zip delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/javadoc-without-allclasses-frame.zip delete mode 100644 src/test/resources/com/gmail/inverseconduit/javadoc/jsoup-allclasses-frame.html diff --git a/javadocs/readme.txt b/javadocs/readme.txt index cd819f5..0146fdb 100644 --- a/javadocs/readme.txt +++ b/javadocs/readme.txt @@ -1,11 +1,23 @@ -Put a ZIP file here named "java8.zip" containing Java's Javadocs (or where ever "BotConfig.JAVADOCS_DIR" points to) +This folder contains ZIP files generated by OakbotDoclet. -The ZIP file should contain the files that are in the "api" directory of the "Documentation" download, found here: -http://www.oracle.com/technetwork/java/javase/documentation/jdk8-doc-downloads-2133158.html +* Each ZIP file contains a collection of XML files, which contain parsed Javadoc information of a particular library. +* Each XML file contains the Javadoc information of a single class. +* The ZIP file also contains a "info.xml" file, which contains information about the library itself. -Make sure that a file called "allclasses-frames.html" is at the root of the ZIP. +======================= -I don't want to commit the real Javadoc file to version control, due to its large file size (~50MB), so I've committed a smaller, sample file instead. +To generate a ZIP file: + +1. Clone and open the OakBot project: http://github.com/mangstadt/OakBot +2. Compile the OakbotDoclet class. You will need to include the JVM's "tools.jar" file on the classpath during compilation. This file can be found at "$JAVA_HOME/lib/tools.jar" +3. Tweak the "build-zip.sh" file as necessary and then execute it. Note that you'll need a copy of the library's source code on your computer. + +======================= + +I don't want to commit the ZIP for the Java 8 API to version control, due to its large file size (~6MB), so I've committed a smaller, sample file instead. The sample ZIP file contains just a single class (java.lang.String). +ZIP files for various libraries (including the core Java 8 API) can be downloaded here: +https://www.dropbox.com/sh/xkf7kua3hzd8xvo/AAC1sOkVTNUE2MKPAXTm28bna?dl=0 + -Michael \ No newline at end of file diff --git a/javadocs/sample.zip b/javadocs/sample.zip index 87d0b62e535f25d82937ee5b18b1b554ea47586f..b01086437d017bbafa5bf784b46c70e106f7c8ac 100644 GIT binary patch literal 9141 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 IQT=E7ABB8k8~^|S literal 27677 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 BL4E)L diff --git a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java index 9017f74..5ac6609 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java +++ b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java @@ -34,6 +34,7 @@ public class DefaultBot extends AbstractBot implements Subscribable commands = new HashSet<>(); + protected final Set listeners = new HashSet<>(); public DefaultBot(ChatInterface chatInterface) { this.chatInterface = chatInterface; @@ -52,6 +53,13 @@ private void processMessageQueue() { } private void processMessage(final ChatMessage chatMessage) { + for (final CommandHandle listener : listeners){ + final String response = listener.execute(chatMessage); + if (response != null){ + chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(chatMessage), response); + } + } + final String trigger = AppContext.INSTANCE.get(BotConfig.class).getTrigger(); if ( !chatMessage.getMessage().startsWith(trigger)) { return; } @@ -62,14 +70,23 @@ private void processMessage(final ChatMessage chatMessage) { public Set getCommands() { return Collections.unmodifiableSet(commands); } + + public Set getListeners(){ + return Collections.unmodifiableSet(listeners); + } @Override public void subscribe(CommandHandle subscriber) { - commands.add(subscriber); + if (subscriber.getName() == null){ + listeners.add(subscriber); + } else { + commands.add(subscriber); + } } @Override public void unSubscribe(CommandHandle subscriber) { + listeners.remove(subscriber); commands.remove(subscriber); } @@ -79,7 +96,9 @@ public void shutdown() { @Override public Collection getSubscriptions() { - return Collections.unmodifiableCollection(commands); + Set set = new HashSet(commands); + set.addAll(listeners); + return set; } } diff --git a/src/main/java/com/gmail/inverseconduit/bot/Program.java b/src/main/java/com/gmail/inverseconduit/bot/Program.java index bf88037..e6a695e 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/Program.java +++ b/src/main/java/com/gmail/inverseconduit/bot/Program.java @@ -102,15 +102,30 @@ private void login() { private void bindDefaultCommands() { bindShutdownCommand(); + bindNumberCommand(); bindJavaDocCommand(); new CoreBotCommands(chatInterface, bot).allCommands().forEach(bot::subscribe); } + + private void bindNumberCommand() { + final Pattern p = Pattern.compile("^\\d+$"); + CommandHandle javaDoc = new CommandHandle.Builder(null, message -> { + Matcher matcher = p.matcher(message.getMessage()); + if (!matcher.find()){ + return null; + } + + int choice = Integer.parseInt(matcher.group(0)); + return javaDocAccessor.showChoice(message, choice); + }).build(); + bot.subscribe(javaDoc); + } private void bindJavaDocCommand() { CommandHandle javaDoc = new CommandHandle.Builder("javadoc", message -> { Matcher matcher = javadocPattern.matcher(message.getMessage()); matcher.find(); - return javaDocAccessor.javadoc(message, matcher.group(1).trim()); + return javaDocAccessor.javadoc(message, matcher.group(1)); }).build(); bot.subscribe(javaDoc); } diff --git a/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java b/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java index 05db77c..5acd815 100644 --- a/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java +++ b/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java @@ -107,7 +107,10 @@ public String execute(ChatMessage message) { @Deprecated public boolean matchesSyntax(String commandCall) { // FIXME: Interimsimplementation. To be removed! - return commandCall.contains(name); + if (commandCall != null && name != null){ + return commandCall.contains(name); + } + return false; } public String getHelpText() { diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java index 6f20c9b..a4e05e2 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java @@ -1,63 +1,144 @@ package com.gmail.inverseconduit.javadoc; +import java.util.Collection; import java.util.List; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + /** * Holds the Javadoc info of a class. * @author Michael Angstadt */ public class ClassInfo { - private final String fullName; + private final ClassName name, superClass; private final String description; - private final String url; private final List modifiers; + private final List interfaces; + private final Multimap methods; private final boolean deprecated; + private final LibraryZipFile zipFile; - public ClassInfo(String fullName, String description, String url, List modifiers, boolean deprecated) { - this.fullName = fullName; - this.description = description; - this.url = url; - this.modifiers = modifiers; - this.deprecated = deprecated; + private ClassInfo(Builder builder) { + name = builder.name; + superClass = builder.superClass; + description = builder.description; + modifiers = builder.modifiers.build(); + interfaces = builder.interfaces.build(); + methods = builder.methods.build(); + deprecated = builder.deprecated; + zipFile = builder.zipFile; } - /** - * Gets the class's fully-qualified name. - * @return the fully-qualified name (e.g. "java.lang.String") - */ - public String getFullName() { - return fullName; + public ClassName getName() { + return name; + } + + public ClassName getSuperClass() { + return superClass; } - /** - * Gets the class's description. - * @return the class description, formatted in SO Chat's markdown language - */ public String getDescription() { return description; } /** - * Gets the URL where this class's Javadocs can be viewed online. - * @return the URL or null if unknown + * Gets the URL to this class's Javadoc page (with frames). + * @return the URL or null if no base URL was given */ - public String getUrl() { - return url; + public String getFrameUrl() { + return zipFile.getFrameUrl(this); } /** - * Gets the modifiers of this class. - * @return the modifiers (e.g. "public", "final", "class") + * Gets the URL to this class's Javadoc page (without frames). + * @return the URL or null if no base URL was given */ + public String getUrl() { + return zipFile.getUrl(this); + } + public List getModifiers() { return modifiers; } - /** - * Gets whether the class is deprecated or not. - * @return true if it's deprecated, false if not - */ + public List getInterfaces() { + return interfaces; + } + + public Collection getMethod(String name) { + return methods.get(name.toLowerCase()); + } + + public Collection getMethods() { + return methods.values(); + } + public boolean isDeprecated() { return deprecated; } + + public LibraryZipFile getZipFile() { + return zipFile; + } + + public static class Builder { + private ClassName name, superClass; + private String description; + private ImmutableList.Builder modifiers = ImmutableList.builder(); + private ImmutableList.Builder interfaces = ImmutableList.builder(); + private ImmutableMultimap.Builder methods = ImmutableMultimap.builder(); + private boolean deprecated = false; + private LibraryZipFile zipFile; + + public Builder name(String full, String simple) { + this.name = new ClassName(full, simple); + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder superClass(String superClass) { + this.superClass = new ClassName(superClass); + return this; + } + + public Builder modifiers(List modifiers) { + this.modifiers.addAll(modifiers); + return this; + } + + public Builder interface_(String interface_) { + interfaces.add(new ClassName(interface_)); + return this; + } + + public Builder interfaces(List interfaces) { + this.interfaces.addAll(interfaces); + return this; + } + + public Builder method(MethodInfo method) { + this.methods.put(method.getName().toLowerCase(), method); + return this; + } + + public Builder deprecated(boolean deprecated) { + this.deprecated = deprecated; + return this; + } + + public Builder zipFile(LibraryZipFile zipFile) { + this.zipFile = zipFile; + return this; + } + + public ClassInfo build() { + return new ClassInfo(this); + } + } } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfoXmlParser.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfoXmlParser.java new file mode 100644 index 0000000..7c4771b --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfoXmlParser.java @@ -0,0 +1,179 @@ +package com.gmail.inverseconduit.javadoc; + +import java.util.Arrays; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.gmail.inverseconduit.utils.DocumentWrapper; + +/** + * Parses {@link ClassInfo} objects from XML documents. + * @author Michael Angstadt + */ +public class ClassInfoXmlParser { + private final DocumentWrapper document; + private final LibraryZipFile zipFile; + + /** + * @param document the XML document to parse + * @param zipFile the ZIP file the class belongs to + */ + public ClassInfoXmlParser(Document document, LibraryZipFile zipFile) { + this.document = new DocumentWrapper(document); + this.zipFile = zipFile; + } + + /** + * Parses the {@link ClassInfo} object out of the XML data. + * @return the parse object + */ + public ClassInfo parse() { + ClassInfo.Builder builder = new ClassInfo.Builder(); + builder.zipFile(zipFile); + + //class name + Element classElement = document.element("/class"); + String fullName = classElement.getAttribute("fullName"); + String simpleName = classElement.getAttribute("simpleName"); + builder.name(fullName, simpleName); + + //modifiers + String value = classElement.getAttribute("modifiers"); + if (!value.isEmpty()) { + builder.modifiers(Arrays.asList(value.split("\\s+"))); + } + + //super class + value = classElement.getAttribute("extends"); + if (!value.isEmpty()) { + builder.superClass(value); + } + + //interfaces + value = classElement.getAttribute("implements"); + if (!value.isEmpty()) { + for (String full : Arrays.asList(value.split("\\s+"))) { + builder.interface_(full); + } + } + + //deprecated + value = classElement.getAttribute("deprecated"); + builder.deprecated(value.isEmpty() ? false : Boolean.parseBoolean(value)); + + //description + Element element = document.element("/class/description"); + if (element != null) { + builder.description(element.getTextContent()); + } + + //constructors + for (Element constructorElement : document.elements("/class/constructors/constructor")) { + MethodInfo info = parseConstructor(constructorElement, simpleName); + builder.method(info); + } + + //methods + for (Element methodElement : document.elements("/class/methods/method")) { + MethodInfo method = parseMethod(methodElement); + builder.method(method); + } + + return builder.build(); + } + + private MethodInfo parseConstructor(Element element, String simpleName) { + MethodInfo.Builder builder = new MethodInfo.Builder(); + + //name + builder.name(simpleName); + + //modifiers + String value = element.getAttribute("modifiers"); + if (!value.isEmpty()) { + builder.modifiers(Arrays.asList(value.split("\\s+"))); + } + + //description + Element descriptionElement = document.element("description", element); + if (descriptionElement != null) { + builder.description(descriptionElement.getTextContent()); + } + + //deprecated + value = element.getAttribute("deprecated"); + builder.deprecated(value.isEmpty() ? false : Boolean.parseBoolean(value)); + + //parameters + for (Element parameterElement : document.elements("parameters/parameter", element)) { + ParameterInfo parameter = parseParameter(parameterElement); + builder.parameter(parameter); + } + + return builder.build(); + } + + private MethodInfo parseMethod(Element element) { + MethodInfo.Builder builder = new MethodInfo.Builder(); + + //name + builder.name(element.getAttribute("name")); + + //modifiers + String value = element.getAttribute("modifiers"); + if (!value.isEmpty()) { + builder.modifiers(Arrays.asList(value.split("\\s+"))); + } + + //description + Element descriptionElement = document.element("description", element); + if (descriptionElement != null) { + builder.description(descriptionElement.getTextContent()); + } + + //return value + value = element.getAttribute("returns"); + if (!value.isEmpty()) { + builder.returnValue(new ClassName(value)); + } + + //deprecated + value = element.getAttribute("deprecated"); + builder.deprecated(value.isEmpty() ? false : Boolean.parseBoolean(value)); + + //parameters + for (Element parameterElement : document.elements("parameters/parameter", element)) { + ParameterInfo parameter = parseParameter(parameterElement); + builder.parameter(parameter); + } + + return builder.build(); + } + + private ParameterInfo parseParameter(Element element) { + String type = element.getAttribute("type"); + + //is it an array? + boolean array = type.endsWith("[]"); + if (array) { + type = type.substring(0, type.length() - 2); + } + boolean varargs = type.endsWith("..."); + if (varargs) { + type = type.substring(0, type.length() - 3); + } + + //is a generic type? (like List) + int pos = type.indexOf('<'); + String generic = (pos < 0) ? null : type.substring(pos); + if (generic != null) { + type = type.substring(0, pos); + } + + //name + String name = element.getAttribute("name"); + + return new ParameterInfo(new ClassName(type), name, array, varargs, generic); + } +} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java new file mode 100644 index 0000000..0bdf60e --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java @@ -0,0 +1,49 @@ +package com.gmail.inverseconduit.javadoc; + +/** + * Represents the name of a class. + * @author Michael Angstadt + */ +public class ClassName { + private final String full, simple; + + /** + * @param full the fully-qualified class name (e.g. "java.lang.String") + */ + public ClassName(String full) { + this.full = full; + + int pos = full.lastIndexOf('.'); + simple = (pos < 0) ? full : full.substring(pos + 1); + } + + /** + * @param full the fully-qualified class name (e.g. "java.lang.String") + * @param simple the simple class name (e.g. "String") + */ + public ClassName(String full, String simple) { + this.full = full; + this.simple = simple; + } + + /** + * Gets the fully-qualified class name + * @return the fully-qualified class name (e.g. "java.lang.String") + */ + public String getFull() { + return full; + } + + /** + * Gets the simple class name. + * @return the simple class name (e.g. "String") + */ + public String getSimple() { + return simple; + } + + @Override + public String toString() { + return full; + } +} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/DescriptionNodeVisitor.java b/src/main/java/com/gmail/inverseconduit/javadoc/DescriptionNodeVisitor.java deleted file mode 100644 index a27cdad..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/DescriptionNodeVisitor.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.util.regex.Pattern; - -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; -import org.jsoup.select.NodeVisitor; - -/** - * Iterates through the description section of a class's Javadoc HTML page, - * converting the description to SO Chat markdown. - * @author Michael Angstadt - */ -public class DescriptionNodeVisitor implements NodeVisitor { - private final StringBuilder sb = new StringBuilder(); - private final Pattern escapeRegex = Pattern.compile("[*_\\[\\]]"); - private boolean inPre = false; - private String prevText; - private String linkUrl, linkTitle, linkText; - - @Override - public void head(Node node, int depth) { - //for (int i = 0; i < depth; i++) { - // System.out.print(' '); - //} - //System.out.println("head " + node.nodeName()); - //if (node instanceof TextNode) { - // for (int i = 0; i < depth; i++) { - // System.out.print(' '); - // } - // System.out.println(((TextNode) node).text()); - //} - - switch (node.nodeName()) { - case "a": - Element element = (Element) node; - String href = element.absUrl("href"); - if (!href.isEmpty()) { - linkUrl = href; - linkTitle = element.attr("title"); - } - break; - case "code": - case "tt": - sb.append("`"); - break; - case "i": - case "em": - sb.append("*"); - break; - case "b": - case "strong": - sb.append("**"); - break; - case "br": - case "p": - sb.append("\n"); - break; - case "pre": - inPre = true; - sb.append("\n"); - break; - case "#text": - TextNode text = (TextNode) node; - String content; - if (inPre) { - content = text.getWholeText(); - } else { - content = text.text(); - content = escapeRegex.matcher(content).replaceAll("\\\\$0"); //escape special chars - } - - //in the jsoup javadocs, it's reading some text nodes twice for some reason - //so, ignore the duplicate text nodes - if (prevText != null && prevText.equals(content)) { - prevText = null; - break; - } - prevText = content; - - if (inLink()) { - linkText = content; - } else { - sb.append(content); - } - - break; - } - } - - @Override - public void tail(Node node, int depth) { - //for (int i = 0; i < depth; i++) { - // System.out.print(' '); - //} - //System.out.println("tail " + node.nodeName()); - - switch (node.nodeName()) { - case "a": - if (inLink()) { - sb.append("[").append(linkText).append("](").append(linkUrl); - if (!linkTitle.isEmpty()) { - sb.append(" \"").append(linkTitle).append("\""); - } - sb.append(")"); - - linkUrl = linkText = linkTitle = null; - } - break; - case "code": - case "tt": - sb.append("`"); - break; - case "i": - case "em": - sb.append("*"); - break; - case "b": - case "strong": - sb.append("**"); - break; - case "p": - sb.append("\n"); - break; - case "pre": - inPre = false; - sb.append("\n"); - break; - } - } - - private boolean inLink() { - return linkUrl != null; - } - - /** - * Gets the {@link StringBuilder} used to hold the description. - * @return the string builder - */ - public StringBuilder getStringBuilder() { - return sb; - } -} \ No newline at end of file diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/Java8PageParser.java b/src/main/java/com/gmail/inverseconduit/javadoc/Java8PageParser.java deleted file mode 100644 index 098f17f..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/Java8PageParser.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.TextNode; - -/** - * Parses the Java 8 Javadocs. - * @author Michael Angstadt - */ -public class Java8PageParser implements PageParser { - @Override - public List parseClassNames(Document document) { - List classNames = new ArrayList<>(); - for (Element element : document.select("ul li a")) { - String url = element.attr("href"); - int dotPos = url.lastIndexOf('.'); - if (dotPos < 0) { - continue; - } - - url = url.substring(0, dotPos); - url = url.replace('/', '.'); - classNames.add(url); - } - return classNames; - } - - @Override - public ClassInfo parseClassPage(Document document, String className) { - String description; - { - Element descriptionElement = document.select(".block").first(); - DescriptionNodeVisitor visitor = new DescriptionNodeVisitor(); - descriptionElement.traverse(visitor); - description = visitor.getStringBuilder().toString().trim(); - } - - String url = getBaseUrl() + "?" + className.replace('.', '/') + ".html"; - - boolean deprecated = false; - List modifiers; - { - Element element = document.select(".typeNameLabel").first(); - if (element == null) { - //it might be an annotation - element = document.select(".memberNameLabel").first(); - } - TextNode textNode = (TextNode) element.siblingNodes().get(element.siblingIndex() - 1); - - //sometimes, other text comes before the modifiers on the previous line - String text = textNode.getWholeText(); - int pos = text.lastIndexOf('\n'); - if (pos >= 0) { - text = text.substring(pos + 1); - } - - modifiers = Arrays.asList(text.trim().split(" ")); - - //look for @Deprecated annotation - Element parent = (Element) textNode.parent(); - for (Element child : parent.children()) { - if ("@Deprecated".equals(child.text())) { - deprecated = true; - break; - } - } - } - - return new ClassInfo(className, description, url, modifiers, deprecated); - } - - @Override - public String getBaseUrl() { - return "https://docs.oracle.com/javase/8/docs/api/"; - } -} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index d1e0c62..8a89adf 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -1,130 +1,623 @@ package com.gmail.inverseconduit.javadoc; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.gmail.inverseconduit.datatype.ChatMessage; +import com.gmail.inverseconduit.utils.ChatBuilder; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +/** + * Handles "javadoc" commands. + */ public class JavaDocAccessor { + private final JavadocDao dao; + private List prevChoices = new ArrayList<>(); + private long prevChoicesPinged = 0; + private final long choiceTimeout = TimeUnit.SECONDS.toMillis(30); - private final JavadocDao dao; - - /** - * @param chatInterface - * interface to the chat - * @param dir - * the directory to the Javadocs folder - * @throws IOException - * if there's a problem reading a Javadoc file - */ - public JavaDocAccessor(Path dir) throws IOException { - dao = new JavadocDao(); - - Path java8Api = dir.resolve("java8.zip"); - if (Files.exists(java8Api)) { - PageLoader loader = new ZipPageLoader(java8Api); - PageParser parser = new Java8PageParser(); - dao.addJavadocApi(loader, parser); - } - else { - //for testing purposes - //this ZIP only has the "java.lang.String" class - Path sample = dir.resolve("sample.zip"); - if (Files.exists(sample)) { - PageLoader loader = new ZipPageLoader(sample); - PageParser parser = new Java8PageParser(); - dao.addJavadocApi(loader, parser); - } - } - } - - public String javadoc(ChatMessage chatMessage, String commandText) { - String response; - try { - response = generateResponse(commandText); - } catch(IOException e) { - throw new RuntimeException("Problem getting Javadoc info.", e); - } - - return ":" + chatMessage.getMessageId() + " " + response; - } - - private String generateResponse(String commandText) throws IOException { - ClassInfo info; - try { - info = dao.getClassInfo(commandText); - } catch(MultipleClassesFoundException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Which one do you mean?"); - for (String name : e.getClasses()) { - sb.append("\n* ").append(name); - } - return sb.toString(); - } - - if (info == null) { return "Sorry, I never heard of that class. :("; } - - StringBuilder sb = new StringBuilder(); - - boolean deprecated = info.isDeprecated(); - for (String modifier : info.getModifiers()) { - boolean italic = false; - switch (modifier) { - case "abstract": - case "final": - italic = true; - break; - case "class": - case "enum": - case "interface": - italic = false; - break; - case "@interface": - italic = false; - modifier = "annotation"; - break; - default: - //ignore all the rest - continue; - } - - if (italic) - sb.append('*'); - if (deprecated) - sb.append("---"); - sb.append("[tag:").append(modifier).append("]"); - if (deprecated) - sb.append("---"); - if (italic) - sb.append('*'); - sb.append(' '); - } - - if (deprecated) - sb.append("---"); - String fullName = info.getFullName(); - String url = info.getUrl(); - if (url == null) { - sb.append("**`").append(fullName).append("`**"); - } - else { - sb.append("[**`").append(fullName).append("`**](").append(url).append(" \"View the Javadocs\")"); - } - if (deprecated) - sb.append("---"); - sb.append(": "); - - //get the class description - String description = info.getDescription(); - int pos = description.indexOf("\n"); - if (pos >= 0) { - //just display the first paragraph - description = description.substring(0, pos); - } - sb.append(description); - - return sb.toString(); - } + /** + * The class modifiers to print in italics when outputting a class to the + * chat. + */ + private final Set classModifiersItalic; + { + ImmutableSet.Builder b = new ImmutableSet.Builder<>(); + b.add("abstract"); + b.add("final"); + classModifiersItalic = b.build(); + } + + /** + * The class modifiers to print when outputting a class to the chat. + */ + private final Set classModifiers; + { + ImmutableSet.Builder b = new ImmutableSet.Builder<>(); + b.add("annotation"); + b.add("class"); + b.add("enum"); + b.add("exception"); + b.add("interface"); + classModifiers = b.build(); + } + + /** + * The method modifiers to ignore when outputting a method to the chat. + */ + private final Set methodModifiersToIgnore; + { + ImmutableSet.Builder b = new ImmutableSet.Builder<>(); + b.add("private"); + b.add("protected"); + b.add("public"); + methodModifiersToIgnore = b.build(); + } + + /** + * @param dir the directory to the Javadocs folder + * @throws IOException if there's a problem reading a Javadoc file + */ + public JavaDocAccessor(Path dir) throws IOException { + dao = new JavadocDao(dir); + } + + /** + * Processes a Javadoc command. + * @param chatMessage the chat message (e.g. "**javadoc: arraylist") + * @param commandText the command text (e.g. "arraylist") + * @return the chat response + */ + public String javadoc(ChatMessage chatMessage, String commandText) { + String response = generateResponse(commandText.trim()); + + ChatBuilder cb = new ChatBuilder(); + cb.reply(chatMessage); + cb.append(response); + return cb.toString(); + } + + private String generateResponse(String commandTextStr) { + if (commandTextStr.isEmpty()) { + return "Type the name of a Java class (e.g. \"java.lang.String\") or a method (e.g. \"Integer#parseInt\")."; + } + + //parse the command + CommandTextParser commandText = new CommandTextParser(commandTextStr); + + ClassInfo info; + try { + info = dao.getClassInfo(commandText.className); + } catch (MultipleClassesFoundException e) { + if (commandText.methodName == null) { + return printClassChoices(e.getClasses()); + } + + //search each class for a method that matches the given signature + Map exactMatches = new HashMap<>(); + Multimap matchingNames = ArrayListMultimap.create(); + for (String className : e.getClasses()) { + try { + ClassInfo classInfo = dao.getClassInfo(className); + MatchingMethods methods = getMatchingMethods(classInfo, commandText.methodName, commandText.parameters); + if (methods.exactSignature != null) { + exactMatches.put(classInfo, methods.exactSignature); + } + matchingNames.putAll(classInfo, methods.matchingName); + } catch (IOException e2) { + throw new RuntimeException("Problem getting Javadoc info.", e2); + } + } + + if (exactMatches.isEmpty() && matchingNames.isEmpty()) { + //no matches found + return "Sorry, I can't find that method. :("; + } + + if (exactMatches.size() == 1) { + //a single, exact match was found! + MethodInfo method = exactMatches.values().iterator().next(); + ClassInfo classInfo = exactMatches.keySet().iterator().next(); + return printMethod(method, classInfo, commandText.paragraph); + } + + //multiple matches were found + Multimap map; + if (exactMatches.size() > 1) { + map = ArrayListMultimap.create(); + for (Map.Entry entry : exactMatches.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + } else { + map = matchingNames; + } + return printMethodChoices(map, commandText.parameters); + } catch (IOException e) { + throw new RuntimeException("Problem getting Javadoc info.", e); + } + + if (info == null) { + //couldn't find the class + return "Sorry, I never heard of that class. :("; + } + + if (commandText.methodName == null) { + //method name not specified, so print the class docs + return printClass(info, commandText.paragraph); + } + + //print the method docs + MatchingMethods matchingMethods; + try { + matchingMethods = getMatchingMethods(info, commandText.methodName, commandText.parameters); + } catch (IOException e) { + throw new RuntimeException("Problem getting Javadoc info.", e); + } + + if (matchingMethods.isEmpty()) { + //no matches found + return "Sorry, I can't find that method. :("; + } + + if (matchingMethods.exactSignature != null) { + //an exact match was found! + return printMethod(matchingMethods.exactSignature, info, commandText.paragraph); + } + + if (matchingMethods.matchingName.size() == 1 && commandText.parameters == null) { + return printMethod(matchingMethods.matchingName.get(0), info, commandText.paragraph); + } + + //print the methods with the same name + Multimap map = ArrayListMultimap.create(); + map.putAll(info, matchingMethods.matchingName); + return printMethodChoices(map, commandText.parameters); + } + + /** + * Called when someone types a number into the chat, in response to the + * javadoc command showing a list of choices. + * @param message the chat message + * @param num the number + * @return the chat response or null to ignore the message + */ + public String showChoice(ChatMessage message, int num) { + if (prevChoicesPinged == 0) { + return null; + } + + boolean timedOut = System.currentTimeMillis() - prevChoicesPinged > choiceTimeout; + if (timedOut) { + return null; + } + + //reset the time-out timer + prevChoicesPinged = System.currentTimeMillis(); + + int index = num - 1; + if (index < 0 || index >= prevChoices.size()) { + ChatBuilder cb = new ChatBuilder(); + cb.reply(message).append("That's not a valid choice."); + return cb.toString(); + + } + + String msg = prevChoices.get(index); + return javadoc(message, msg); + } + + /** + * Prints the Javadoc info of a particular method. + * @param methodinfo the method + * @param classInfo the class that the method belongs to + * @param paragraph the paragraph to print + * @return the chat response + */ + private String printMethod(MethodInfo methodInfo, ClassInfo classInfo, int paragraph) { + ChatBuilder cb = new ChatBuilder(); + if (paragraph == 1) { + //print library name + LibraryZipFile zipFile = classInfo.getZipFile(); + if (zipFile != null) { + String name = zipFile.getName(); + if (name != null && !name.equalsIgnoreCase("java")) { + name = name.replace(' ', '-'); + cb.bold(); + cb.tag(name); + cb.bold(); + cb.append(' '); + } + } + + //print modifiers + boolean deprecated = methodInfo.isDeprecated(); + for (String modifier : methodInfo.getModifiers()) { + if (methodModifiersToIgnore.contains(modifier)) { + continue; + } + + if (deprecated) cb.strike(); + cb.tag(modifier); + if (deprecated) cb.strike(); + cb.append(' '); + } + + //print signature + if (deprecated) cb.strike(); + String signature = methodInfo.getSignatureString(); + String url = classInfo.getUrl(); + if (url == null) { + cb.bold().code(signature).bold(); + } else { + url += "#" + methodInfo.getUrlAnchor(); + cb.link(new ChatBuilder().bold().code(signature).bold().toString(), url); + } + if (deprecated) cb.strike(); + cb.append(": "); + } + + //print the method description + String description = methodInfo.getDescription(); + Paragraphs paragraphs = new Paragraphs(description); + paragraphs.append(paragraph, cb); + return cb.toString(); + } + + /** + * Prints the methods to choose from when multiple methods are found. + * @param matchingMethods the methods to choose from + * @param methodParams the parameters of the method or null if no parameters + * were specified + * @return the chat response + */ + private String printMethodChoices(Multimap matchingMethods, List methodParams) { + prevChoices = new ArrayList<>(); + prevChoicesPinged = System.currentTimeMillis(); + ChatBuilder cb = new ChatBuilder(); + if (matchingMethods.size() == 1) { + if (methodParams == null) { + cb.append("Did you mean this one? (type the number)"); + } else { + if (methodParams.isEmpty()) { + cb.append("I couldn't find a zero-arg signature for that method."); + } else { + cb.append("I couldn't find a signature with "); + cb.append((methodParams.size() == 1) ? "that parameter." : "those parameters."); + } + cb.append(" Did you mean this one? (type the number)"); + } + } else { + if (methodParams == null) { + cb.append("Which one do you mean? (type the number)"); + } else { + if (methodParams.isEmpty()) { + cb.append("I couldn't find a zero-arg signature for that method."); + } else { + cb.append("I couldn't find a signature with "); + cb.append((methodParams.size() == 1) ? "that parameter." : "those parameters."); + } + cb.append(" Did you mean one of these? (type the number)"); + } + } + + int count = 1; + for (Map.Entry entry : matchingMethods.entries()) { + ClassInfo classInfo = entry.getKey(); + MethodInfo methodInfo = entry.getValue(); + + String signature; + { + StringBuilder sb = new StringBuilder(); + sb.append(classInfo.getName().getFull()).append("#").append(methodInfo.getName()); + + List paramList = new ArrayList<>(); + for (ParameterInfo param : methodInfo.getParameters()) { + paramList.add(param.getType().getSimple() + (param.isArray() ? "[]" : "")); + } + sb.append('(').append(String.join(", ", paramList)).append(')'); + + signature = sb.toString(); + } + + cb.nl().append(count + "").append(". ").append(signature); + prevChoices.add(signature); + count++; + } + return cb.toString(); + } + + /** + * Prints the classes to choose from when multiple class are found. + * @param classes the fully-qualified names of the classes + * @return the chat response + */ + private String printClassChoices(Collection classes) { + List choices = new ArrayList<>(classes); + Collections.sort(choices); + prevChoices = choices; + prevChoicesPinged = System.currentTimeMillis(); + + ChatBuilder cb = new ChatBuilder(); + cb.append("Which one do you mean? (type the number)"); + + int count = 1; + for (String name : choices) { + cb.nl().append(count + "").append(". ").append(name); + count++; + } + + return cb.toString(); + } + + /** + * Prints the description of a class. + * @param info the class info + * @param paragraph the paragraph to print + * @return the chat response + */ + private String printClass(ClassInfo info, int paragraph) { + ChatBuilder cb = new ChatBuilder(); + if (paragraph == 1) { + //print the library name + LibraryZipFile zipFile = info.getZipFile(); + if (zipFile != null) { + String name = zipFile.getName(); + if (name != null && !name.equalsIgnoreCase("Java")) { + name = name.replace(" ", "-"); + cb.bold(); + cb.tag(name); + cb.bold(); + cb.append(' '); + } + } + + //print modifiers + boolean deprecated = info.isDeprecated(); + for (String modifier : info.getModifiers()) { + boolean italic = classModifiersItalic.contains(modifier); + if (!italic && !classModifiers.contains(modifier)) { + continue; + } + + if (italic) cb.italic(); + if (deprecated) cb.strike(); + cb.tag(modifier); + if (deprecated) cb.strike(); + if (italic) cb.italic(); + cb.append(' '); + } + + //print class name + if (deprecated) cb.strike(); + String fullName = info.getName().getFull(); + String url = info.getFrameUrl(); + if (url == null) { + cb.bold().code(fullName).bold(); + } else { + cb.link(new ChatBuilder().bold().code(fullName).bold().toString(), url); + } + if (deprecated) cb.strike(); + cb.append(": "); + } + + //print the class description + String description = info.getDescription(); + Paragraphs paragraphs = new Paragraphs(description); + paragraphs.append(paragraph, cb); + return cb.toString(); + } + + /** + * Finds the methods in a given class that matches the given method + * signature. + * @param info the class to search + * @param methodName the name of the method to search for + * @param methodParameters the parameters that the method should have, or + * null not to look at the parameters. + * @return the matching methods + * @throws IOException if there's a problem loading Javadoc info + */ + private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, List methodParameters) throws IOException { + MatchingMethods matchingMethods = new MatchingMethods(); + Set matchingNameSignatures = new HashSet<>(); + + //search the class, all its parent classes, and all its interfaces and the interfaces of its super classes + LinkedList stack = new LinkedList<>(); + stack.add(info); + + while (!stack.isEmpty()) { + ClassInfo curInfo = stack.removeLast(); + for (MethodInfo curMethod : curInfo.getMethods()) { + if (!curMethod.getName().equalsIgnoreCase(methodName)) { + //name doesn't match + continue; + } + + String signature = curMethod.getSignature(); + if (matchingNameSignatures.contains(signature)) { + //this method is already in the matching name list + continue; + } + + matchingNameSignatures.add(signature); + matchingMethods.matchingName.add(curMethod); + + if (methodParameters == null) { + //user is not searching based on parameters + continue; + } + + List curParameters = curMethod.getParameters(); + if (curParameters.size() != methodParameters.size()) { + //parameter size doesn't match + continue; + } + + //check the parameters + boolean exactMatch = true; + for (int i = 0; i < curParameters.size(); i++) { + ParameterInfo curParameter = curParameters.get(i); + String curParameterName = curParameter.getType().getSimple() + (curParameter.isArray() ? "[]" : ""); + + String methodParameter = methodParameters.get(i); + + if (!curParameterName.equalsIgnoreCase(methodParameter)) { + //parameter types don't match + exactMatch = false; + break; + } + } + if (exactMatch) { + matchingMethods.exactSignature = curMethod; + } + } + + //add parent class to the stack + ClassName superClass = curInfo.getSuperClass(); + if (superClass != null) { + ClassInfo superClassInfo = dao.getClassInfo(superClass.getFull()); + if (superClassInfo != null) { + stack.add(superClassInfo); + } + } + + //add interfaces to the stack + for (ClassName interfaceName : curInfo.getInterfaces()) { + ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFull()); + if (interfaceInfo != null) { + stack.add(interfaceInfo); + } + } + } + + return matchingMethods; + } + + /** + * Parses a javadoc chat command (e.g. "arraylist#add") + */ + static class CommandTextParser { + private final static Pattern messageRegex = Pattern.compile("(.*?)(\\((.*?)\\))?(#(.*?)(\\((.*?)\\))?)?(\\s+(.*?))?$"); + + private final String className, methodName; + private final List parameters; + private final int paragraph; + + /** + * @param message the command text + */ + public CommandTextParser(String message) { + Matcher m = messageRegex.matcher(message); + m.find(); + + className = m.group(1); + + if (m.group(2) != null) { //e.g. java.lang.string(string, string) + int dot = className.lastIndexOf('.'); + String simpleName = (dot < 0) ? className : className.substring(dot + 1); + methodName = simpleName; + } else { + methodName = m.group(5); //e.g. java.lang.string#indexOf(int) + } + + String parametersStr = m.group(4); //e.g. java.lang.string(string, string) + if (parametersStr == null || parametersStr.startsWith("#")) { + parametersStr = m.group(7); //e.g. java.lang.string#string(string, string) + if (parametersStr == null) { + parametersStr = m.group(3); + } + } + if (parametersStr == null || parametersStr.equals("*")) { + parameters = null; + } else if (parametersStr.isEmpty()) { + parameters = Collections.emptyList(); + } else { + parameters = Arrays.asList(parametersStr.split("\\s*,\\s*")); + } + + int paragraph; + try { + paragraph = Integer.parseInt(m.group(9)); + if (paragraph < 1) { + paragraph = 1; + } + } catch (NumberFormatException e) { + paragraph = 1; + } + this.paragraph = paragraph; + } + + public String getClassName() { + return className; + } + + public String getMethodName() { + return methodName; + } + + public List getParameters() { + return parameters; + } + + public int getParagraph() { + return paragraph; + } + } + + private static class MatchingMethods { + private MethodInfo exactSignature; + private final List matchingName = new ArrayList<>(); + + public boolean isEmpty() { + return exactSignature == null && matchingName.isEmpty(); + } + } + + private static class Paragraphs { + private final String paragraphs[]; + + public Paragraphs(String text) { + paragraphs = text.split("\n\n"); + } + + public int count() { + return paragraphs.length; + } + + public String get(int num) { + return paragraphs[num - 1]; + } + + /** + * Appends a paragraph to a {@link ChatBuilder}. + * @param num the paragraph number + * @param cb the chat builder + */ + public void append(int num, ChatBuilder cb) { + if (num > count()) { + num = count(); + } + + cb.append(get(num)); + if (count() > 1) { + cb.append(" (").append(num + "").append("/").append(count() + "").append(")"); + } + } + } } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java index beb567e..1b80f12 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java @@ -1,50 +1,90 @@ package com.gmail.inverseconduit.javadoc; import java.io.IOException; -import java.util.ArrayList; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.List; +import java.util.Iterator; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import com.sun.nio.file.SensitivityWatchEventModifier; /** - * Retrieves class information from Javadoc files. + * Retrieves class information from Javadoc ZIP files, generated by + * OakbotDoclet. * @author Michael Angstadt */ public class JavadocDao { - private final Multimap simpleToFullClassNames = HashMultimap.create(); - private final Map cache = Collections.synchronizedMap(new HashMap<>()); - private final List libraries = new ArrayList<>(); + private static final Logger logger = Logger.getLogger(JavadocDao.class.getName()); /** - * Adds a library's Javadoc API to this DAO. - * @param loader the page loader - * @param parser the page parser - * @throws IOException if there's a problem reading from the Javadocs + * Maps each Javadoc ZIP file to the list of classes it contains. */ - public void addJavadocApi(PageLoader loader, PageParser parser) throws IOException { - addJavadocApi(new JavadocLibrary(loader, parser)); + private final Multimap libraryClasses = HashMultimap.create(); + + /** + * Maps class name aliases to their fully qualified names. For example, maps + * "string" to "java.lang.String". Note that there can be more than one + * class name mapped to an alias (for example "list" is mapped to + * "java.util.List" and "java.awt.List"). + */ + private final Multimap aliases = HashMultimap.create(); + + /** + * Caches class info that was parsed from a Javadoc ZIP file. The key is the + * fully-qualified name of the class, and the value is the parsed Javadoc + * info. + */ + private final Map cache = new HashMap<>(); + + /** + * @param dir the path to where the Javadoc ZIP files are stored. + * @throws IOException if there's a problem reading the ZIP files + */ + public JavadocDao(Path dir) throws IOException { + try (DirectoryStream stream = Files.newDirectoryStream(dir, JavadocDao::isZipFile)) { + for (Path path : stream) { + addApi(path); + } + } + + WatchThread watchThread = new WatchThread(dir); + watchThread.start(); } /** - * Adds a library's Javadoc API to this DAO. - * @param library the Javadoc library - * @throws IOException if there's a problem reading from the parser + * Adds a Javadoc ZIP file to the DAO. + * @param zipFile the zip file (generated by OakbotDoclet) + * @throws IOException if there was a problem reading the ZIP file */ - public void addJavadocApi(JavadocLibrary library) throws IOException { + private void addApi(Path zipFile) throws IOException { //add all the class names to the simple name index - for (String fullName : library.getAllClassNames()) { - int dotPos = fullName.lastIndexOf('.'); - String simpleName = fullName.substring(dotPos + 1); + LibraryZipFile zip = new LibraryZipFile(zipFile); + Iterator it = zip.getClasses(); + synchronized (this) { + while (it.hasNext()) { + ClassName className = it.next(); + String fullName = className.getFull(); + String simpleName = className.getSimple(); - simpleToFullClassNames.put(simpleName.toLowerCase(), fullName); + aliases.put(simpleName.toLowerCase(), fullName); + aliases.put(simpleName, fullName); + aliases.put(fullName.toLowerCase(), fullName); + aliases.put(fullName, fullName); + libraryClasses.put(zip, fullName); + } } - - libraries.add(library); } /** @@ -56,29 +96,34 @@ public void addJavadocApi(JavadocLibrary library) throws IOException { * @throws MultipleClassesFoundException if a simple name was passed into * this method and multiple classes were found that have that name */ - public ClassInfo getClassInfo(String className) throws IOException, MultipleClassesFoundException { - //convert simple name to fully-qualified name - if (!className.contains(".")) { - Collection names = simpleToFullClassNames.get(className.toLowerCase()); - if (names.isEmpty()) { - return null; - } + public synchronized ClassInfo getClassInfo(String className) throws IOException, MultipleClassesFoundException { + Collection names = aliases.get(className); + if (names.isEmpty()) { + //try case-insensitive search + names = aliases.get(className.toLowerCase()); + } - if (names.size() > 1) { - throw new MultipleClassesFoundException(names); - } + if (names.isEmpty()) { + //no class found + return null; + } - className = names.iterator().next(); + if (names.size() > 1) { + //multiple classes found + throw new MultipleClassesFoundException(names); } + className = names.iterator().next(); + //check the cache ClassInfo info = cache.get(className); if (info != null) { return info; } - for (JavadocLibrary library : libraries) { - info = library.getClassInfo(className); + //parse the class info from the Javadocs + for (LibraryZipFile zip : libraryClasses.keys()) { + info = zip.getClassInfo(className); if (info != null) { cache.put(className, info); return info; @@ -87,4 +132,110 @@ public ClassInfo getClassInfo(String className) throws IOException, MultipleClas return null; } + + private class WatchThread extends Thread { + private final Path dir; + private final WatchService watcher; + + /** + * @param dir the directory to watch + * @throws IOException if there's a problem watching the directory + */ + public WatchThread(Path dir) throws IOException { + setName(getClass().getSimpleName()); + setDaemon(true); + + this.dir = dir; + watcher = FileSystems.getDefault().newWatchService(); + dir.register(watcher, new WatchEvent.Kind[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY }, SensitivityWatchEventModifier.HIGH); + } + + @Override + public void run() { + while (true) { + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException e) { + return; + } + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + if (kind == StandardWatchEventKinds.OVERFLOW) { + continue; + } + + @SuppressWarnings("unchecked") + Path file = ((WatchEvent) event).context(); + if (!isZipFile(file)) { + continue; + } + + file = dir.resolve(file); + + if (kind == StandardWatchEventKinds.ENTRY_CREATE) { + add(file); + continue; + } + + if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + remove(file); + continue; + } + + if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { + remove(file); + add(file); + continue; + } + } + + boolean valid = key.reset(); + if (!valid) { + break; + } + } + } + + private void add(Path file) { + logger.info("Loading ZIP file " + file + "..."); + try { + addApi(file); + logger.info("ZIP file " + file + " loaded."); + } catch (IOException e) { + logger.log(Level.SEVERE, "Could not parse Javadoc ZIP file. ZIP file was not added to the JavadocDao.", e); + } + } + + private void remove(Path file) { + logger.info("Removing ZIP file " + file + "..."); + Path fileName = file.getFileName(); + + synchronized (JavadocDao.this) { + //find the corresponding LibraryZipFile object + LibraryZipFile found = null; + for (LibraryZipFile zip : libraryClasses.keys()) { + if (zip.getPath().getFileName().equals(fileName)) { + found = zip; + break; + } + } + if (found == null) { + logger.warning("Tried to remove ZIP file \"" + file + "\", but it was not found in the JavadocDao."); + return; + } + + Collection classNames = libraryClasses.removeAll(found); + aliases.values().removeAll(classNames); + cache.keySet().removeAll(classNames); + } + + logger.info("ZIP file " + file + " removed."); + } + } + + private static boolean isZipFile(Path file) { + return file.getFileName().toString().toLowerCase().endsWith(".zip"); + } } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocLibrary.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocLibrary.java deleted file mode 100644 index a69780f..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocLibrary.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; - -/** - * Represents a Javadoc library. - * @author Michael Angstadt - */ -public class JavadocLibrary { - private final PageLoader loader; - private final PageParser parser; - - /** - * @param loader the page loader - * @param parser the page parser - */ - public JavadocLibrary(PageLoader loader, PageParser parser) { - this.loader = loader; - this.parser = parser; - } - - /** - * Gets the fully-qualified names of all the classes that are contained - * within this library. - * @return the fully-qualified class names - * @throws IOException if there was a problem reading from the Javadocs - */ - public List getAllClassNames() throws IOException { - Document document; - try (InputStream in = loader.getAllClassesFile()) { - if (null == in) { - return Collections.EMPTY_LIST; - } - document = Jsoup.parse(in, "UTF-8", parser.getBaseUrl()); - } - return parser.parseClassNames(document); - } - - /** - * Gets the Javadoc documentation of a class. - * @param className the fully-qualified class name (e.g. "java.lang.String") - * @return the documentation or null if the class was not found - * @throws IOException if there was a problem reading from the Javadocs - */ - public ClassInfo getClassInfo(String className) throws IOException { - Document document; - try (InputStream in = loader.getClassPage(className)) { - if (null == in) { - return null; - } - document = Jsoup.parse(in, "UTF-8", parser.getBaseUrl()); - } - return parser.parseClassPage(document, className); - } -} \ No newline at end of file diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JsoupPageParser.java b/src/main/java/com/gmail/inverseconduit/javadoc/JsoupPageParser.java deleted file mode 100644 index ad50e26..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JsoupPageParser.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; - -/** - * Parses the Jsoup Javadocs. - * @author Michael Angstadt - */ -public class JsoupPageParser implements PageParser { - @Override - public List parseClassNames(Document document) { - List classNames = new ArrayList<>(); - for (Element element : document.select("a")) { - String url = element.attr("href"); - int dotPos = url.lastIndexOf('.'); - if (dotPos < 0) { - continue; - } - - url = url.substring(0, dotPos); - url = url.replace('/', '.'); - classNames.add(url); - } - return classNames; - } - - @Override - public ClassInfo parseClassPage(Document document, String className) { - String description; - { - JsoupDescriptionNodeVisitor visitor = new JsoupDescriptionNodeVisitor(); - document.traverse(visitor); - description = visitor.getStringBuilder().toString().trim(); - } - - String url = getBaseUrl() + "?" + className.replace('.', '/') + ".html"; - - List modifiers; - { - Element element = document.select("dt").get(1); - modifiers = Arrays.asList(element.text().trim().split(" ")); - String simpleName = className.substring(className.lastIndexOf('.') + 1); - int pos = modifiers.indexOf(simpleName); - modifiers = modifiers.subList(0, pos); - } - - return new ClassInfo(className, description, url, modifiers, false); - } - - @Override - public String getBaseUrl() { - return "http://jsoup.org/apidocs/"; - } - - private static class JsoupDescriptionNodeVisitor extends DescriptionNodeVisitor { - private Boolean inDescription; - - @Override - public void head(Node node, int depth) { - if (inDescription == Boolean.FALSE) { - return; - } - - if (inDescription == null) { - if ("p".equals(node.nodeName())) { - //the first

signals the start of the description - inDescription = Boolean.TRUE; - } else { - return; - } - } - - if ("dl".equals(node.nodeName())) { - inDescription = false; - return; - } - - super.head(node, depth); - } - - @Override - public void tail(Node node, int depth) { - if (inDescription == Boolean.TRUE) { - super.head(node, depth); - } - } - } -} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java new file mode 100644 index 0000000..b591f94 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java @@ -0,0 +1,229 @@ +package com.gmail.inverseconduit.javadoc; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.gmail.inverseconduit.utils.DocumentWrapper; + +/** + * Represents a ZIP file that was generated by OakbotDoclet, which contains + * Javadoc information. + * @author Michael Angstadt + */ +public class LibraryZipFile { + private static final String extension = ".xml"; + private static final String infoFileName = "info" + extension; + + private final Path file; + private final String baseUrl, name, version, projectUrl; + + public LibraryZipFile(Path file) throws IOException { + this.file = file.toRealPath(); + + try (FileSystem fs = FileSystems.newFileSystem(file, null)) { + Path info = fs.getPath("/" + infoFileName); + if (!Files.exists(info)) { + baseUrl = name = version = projectUrl = null; + return; + } + + Element infoElement; + try (InputStream in = Files.newInputStream(info)) { + DocumentWrapper document = new DocumentWrapper(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in)); + infoElement = document.element("/info"); + } catch (ParserConfigurationException e) { + //should never be thrown + throw new RuntimeException(e); + } catch (SAXException e) { + //XML parse error + throw new IOException(e); + } + + if (infoElement == null) { + baseUrl = name = version = projectUrl = null; + return; + } + + String name = infoElement.getAttribute("name"); + this.name = name.isEmpty() ? null : name; + + String baseUrl = infoElement.getAttribute("baseUrl"); + if (baseUrl.isEmpty()) { + this.baseUrl = null; + } else { + this.baseUrl = baseUrl + (baseUrl.endsWith("/") ? "" : "/"); + } + + String projectUrl = infoElement.getAttribute("projectUrl"); + this.projectUrl = projectUrl.isEmpty() ? null : projectUrl; + + String version = infoElement.getAttribute("version"); + this.version = version.isEmpty() ? null : version; + } + } + + /** + * Gets the URL to a class's Javadoc page (with frames). + * @param info the class + * @return the URL or null if no base URL was given + */ + public String getFrameUrl(ClassInfo info) { + if (baseUrl == null) { + return null; + } + return baseUrl + "index.html?" + info.getName().getFull().replace('.', '/') + ".html"; + } + + /** + * Gets the URL to a class's Javadoc page (without frames). + * @param info the class + * @return the URL or null if no base URL was given + */ + public String getUrl(ClassInfo info) { + if (baseUrl == null) { + return null; + } + return baseUrl + info.getName().getFull().replace('.', '/') + ".html"; + } + + /** + * Gets a list of all classes that are in the library. + * @return the fully-qualified names of all the classes + * @throws IOException if there's a problem reading the ZIP file + */ + public Iterator getClasses() throws IOException { + final FileSystem fs = FileSystems.newFileSystem(file, null); + final DirectoryStream stream = Files.newDirectoryStream(fs.getPath("/"), entry -> { + String name = entry.getFileName().toString(); + if (!name.endsWith(extension)) { + return false; + } + + return !name.equals(infoFileName); + }); + + final Iterator it = stream.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + boolean hasNext = it.hasNext(); + if (!hasNext) { + try { + stream.close(); + } catch (IOException e) { + //ignore + } + try { + fs.close(); + } catch (IOException e) { + //ignore + } + } + + return hasNext; + } + + @Override + public ClassName next() { + Path file = it.next(); + String fileName = file.getFileName().toString(); + String fullName = fileName.substring(0, fileName.length() - extension.length()); + return new ClassName(fullName); + } + }; + } + + /** + * Gets the parsed XML DOM of the given class. + * @param fullName the fully-qualifed class name (e.g. "java.lang.String") + * @return the XML DOM or null if the class was not found + * @throws IOException if there was a problem reading from the ZIP file or + * parsing the XML + */ + public ClassInfo getClassInfo(String fullName) throws IOException { + try (FileSystem fs = FileSystems.newFileSystem(file, null)) { + Path path = fs.getPath(fullName + extension); + if (!Files.exists(path)) { + return null; + } + + Document document; + try (InputStream in = Files.newInputStream(path)) { + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); + } catch (SAXException | ParserConfigurationException e) { + throw new IOException(e); + } + + return new ClassInfoXmlParser(document, this).parse(); + } + } + + /** + * Gets the base URL of this library's Javadocs. + * @return the base URL or null if none was defined + */ + public String getBaseUrl() { + return baseUrl; + } + + /** + * Gets the name of this library. + * @return the name (e.g. "jsoup") or null if none was defined + */ + public String getName() { + return name; + } + + /** + * Gets the version number of this library. + * @return the version number (e.g. "1.8.1") or null if none was defined + */ + public String getVersion() { + return version; + } + + /** + * Gets the URL to the library's webpage. + * @return the URL or null if none was defined + */ + public String getProjectUrl() { + return projectUrl; + } + + public Path getPath() { + return file; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((file == null) ? 0 : file.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + LibraryZipFile other = (LibraryZipFile) obj; + if (file == null) { + if (other.file != null) return false; + } else if (!file.equals(other.file)) return false; + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java new file mode 100644 index 0000000..26c67a0 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java @@ -0,0 +1,181 @@ +package com.gmail.inverseconduit.javadoc; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.ImmutableList; + +/** + * Contains information on a method. + * @author Michael Angstadt + */ +public class MethodInfo { + private final String name, description, urlAnchor; + private final List modifiers; + private final List parameters; + private final ClassName returnValue; + private final boolean deprecated; + + private MethodInfo(Builder builder) { + name = builder.name; + modifiers = builder.modifiers.build(); + parameters = builder.parameters.build(); + description = builder.description; + returnValue = builder.returnValue; + deprecated = builder.deprecated; + + StringBuilder sb = new StringBuilder(); + sb.append(name).append('-'); + List fullNames = new ArrayList<>(); + for (ParameterInfo parameter : parameters) { + String fullName = parameter.getType().getFull(); + if (parameter.isArray()) { + fullName += ":A"; + } + if (parameter.isVarargs()) { + fullName += "..."; + } + fullNames.add(fullName); + } + sb.append(String.join("-", fullNames)); + sb.append('-'); + urlAnchor = sb.toString(); + } + + /** + * Gets the method name + * @return the method name + */ + public String getName() { + return name; + } + + /** + * Gets the method's parameters + * @return the method's parameters + */ + public List getParameters() { + return parameters; + } + + /** + * Gets the method's Javadoc description. + * @return the description in SO-Chat Markdown syntax + */ + public String getDescription() { + return description; + } + + /** + * Gets the method modifiers. + * @return the method modifiers (e.g. "public", "static") + */ + public List getModifiers() { + return modifiers; + } + + /** + * Gets this method's Javadoc URL anchor. + * @return the URL anchor (e.g. "substring-int-int-") + */ + public String getUrlAnchor() { + return urlAnchor; + } + + /** + * Gets whether this method is deprecated or not. + * @return true if it's deprecated, false if not + */ + public boolean isDeprecated() { + return deprecated; + } + + /** + * Gets a string that uniquely identifies the method signature, as the + * compiler would. + * @return the signature string (only includes the method name and the + * fully-qualified names of the parameters, e.g. "substring(int, int)") + */ + public String getSignature() { + List params = new ArrayList<>(); + for (ParameterInfo parameter : parameters) { + params.add(parameter.getType().getFull() + (parameter.isArray() ? "[]" : "")); + } + return name + "(" + String.join(", ", params) + ")"; + } + + /** + * Gets the signature string to display in the chat. + * @return the signature string for the chat + */ + public String getSignatureString() { + StringBuilder sb = new StringBuilder(); + + if (returnValue != null) { + sb.append(returnValue.getSimple()).append(' '); + } + sb.append(name); + + List params = new ArrayList<>(); + for (ParameterInfo parameter : parameters) { + String type = parameter.getType().getSimple(); + String generic = parameter.getGeneric(); + if (generic != null) { + type += generic; + } + if (parameter.isArray()) { + type += "[]"; + } + if (parameter.isVarargs()) { + type += "..."; + } + params.add(type + " " + parameter.getName()); + } + sb.append('(').append(String.join(", ", params)).append(')'); + + return sb.toString(); + } + + public static class Builder { + private String name; + private ImmutableList.Builder modifiers = ImmutableList.builder(); + private ImmutableList.Builder parameters = ImmutableList.builder(); + private String description; + private ClassName returnValue; + private boolean deprecated; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder modifiers(List modifiers) { + this.modifiers.addAll(modifiers); + return this; + } + + public Builder parameter(ParameterInfo parameter) { + this.parameters.add(parameter); + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder returnValue(ClassName returnValue) { + this.returnValue = returnValue; + return this; + } + + public Builder deprecated(boolean deprecated) { + this.deprecated = deprecated; + return this; + } + + public MethodInfo build() { + return new MethodInfo(this); + } + } +} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/PageLoader.java b/src/main/java/com/gmail/inverseconduit/javadoc/PageLoader.java deleted file mode 100644 index 696bf35..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/PageLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Loads Javadoc HTML pages - * @author Michael Angstadt - */ -public interface PageLoader { - /** - * Gets an input stream to the HTML page for a given class. - * @param className the fully qualified class name (e.g. "java.lang.String") - * @return an input stream to the HTML page or null if the class was not - * found - * @throws IOException if there's a problem reading the page - */ - InputStream getClassPage(String className) throws IOException; - - /** - * Gets the HTML file that list all the classes. - * @return an input stream to the HTML file - * @throws IOException if there's a problem reading the file - */ - InputStream getAllClassesFile() throws IOException; -} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/PageParser.java b/src/main/java/com/gmail/inverseconduit/javadoc/PageParser.java deleted file mode 100644 index 29eb751..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/PageParser.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import java.util.List; - -import org.jsoup.nodes.Document; - -/** - * Parses Javadoc HTML pages. - * @author Michael Angstadt - */ -public interface PageParser { - /** - * Parses the fully-qualified names of all the classes. - * @param document the HTML document - * @return the fully qualified names of each class names - */ - public List parseClassNames(Document document); - - /** - * Parses a class page. - * @param document the HTML document - * @param className the fully-qualified class name - * @return the class info - */ - public ClassInfo parseClassPage(Document document, String className); - - /** - * Gets the base URL to use when parsing the document. - * @return the base URL - */ - public String getBaseUrl(); -} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java new file mode 100644 index 0000000..2028040 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java @@ -0,0 +1,39 @@ +package com.gmail.inverseconduit.javadoc; + +/** + * Contains information on a method parameter. + * @author Michael Angstadt + */ +public class ParameterInfo { + private final ClassName type; + private final String name, generic; + private final boolean array, varargs; + + public ParameterInfo(ClassName type, String name, boolean array, boolean varargs, String generic) { + this.type = type; + this.name = name; + this.array = array; + this.varargs = varargs; + this.generic = generic; + } + + public ClassName getType() { + return type; + } + + public String getName() { + return name; + } + + public boolean isArray() { + return array; + } + + public boolean isVarargs() { + return varargs; + } + + public String getGeneric() { + return generic; + } +} diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ZipPageLoader.java b/src/main/java/com/gmail/inverseconduit/javadoc/ZipPageLoader.java deleted file mode 100644 index 6f1e8b7..0000000 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ZipPageLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import static org.apache.commons.io.IOUtils.closeQuietly; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * Loads Javadoc pages from a ZIP file. The root the ZIP file must contain the - * "allclasses-frame.html" file. - * @author Michael Angstadt - */ -public class ZipPageLoader implements PageLoader { - private static final String allClassesFrameFileName = "allclasses-frame.html"; - private final Path file; - - /** - * @param file the path to the ZIP file. - * @throws IOException if there's a problem reading the ZIP file - * @throws IllegalArgumentException if there is no "allclasses-frame.html" - * file at the root - */ - public ZipPageLoader(Path file) throws IOException { - //make sure it contains the "allclasses-frame.html" file - try (FileSystem fs = FileSystems.newFileSystem(file, null)) { - Path allClassesFile = fs.getPath("/" + allClassesFrameFileName); - if (!Files.exists(allClassesFile)) { - throw new IllegalArgumentException("\"" + allClassesFrameFileName + "\" not found in ZIP root."); - } - } - - this.file = file; - } - - @Override - public InputStream getClassPage(String className) throws IOException { - Path htmlFile = Paths.get("/" + className.replace('.', '/') + ".html"); - FileSystem fs = FileSystems.newFileSystem(file, null); - Path path = fs.getPath(htmlFile.toString()); - if (Files.exists(path)) { - return new ZipFileInputStream(fs, path); - } - - fs.close(); - return null; - } - - @Override - public InputStream getAllClassesFile() throws IOException { - FileSystem fs = FileSystems.newFileSystem(file, null); - Path allClassesFile = fs.getPath("/" + allClassesFrameFileName); - return new ZipFileInputStream(fs, allClassesFile); - } - - private static class ZipFileInputStream extends InputStream { - private final FileSystem fs; - private final InputStream in; - - public ZipFileInputStream(FileSystem fs, Path path) throws IOException { - this.fs = fs; - this.in = Files.newInputStream(path); - } - - @Override - public int read() throws IOException { - return in.read(); - } - - @Override - public void close() throws IOException { - closeQuietly(in); - closeQuietly(fs); - } - } -} diff --git a/src/main/java/com/gmail/inverseconduit/utils/ChatBuilder.java b/src/main/java/com/gmail/inverseconduit/utils/ChatBuilder.java new file mode 100644 index 0000000..c37e5e4 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/utils/ChatBuilder.java @@ -0,0 +1,196 @@ +package com.gmail.inverseconduit.utils; + +import com.gmail.inverseconduit.datatype.ChatMessage; + +/** + * Helper class for building chat messages that have SO Chat markdown. + * @author Michael Angstadt + * @see Formatting + * FAQ + */ +public class ChatBuilder implements CharSequence { + private final StringBuilder sb; + + /** + * Creates a new chat builder. + */ + public ChatBuilder() { + sb = new StringBuilder(); + } + + /** + * Creates a new chat builder. + * @param text the string to populate it with + */ + public ChatBuilder(String text) { + sb = new StringBuilder(text); + } + + /** + * Appends the character sequence for "fixed font". + * @return this + */ + public ChatBuilder fixed() { + return append(" "); + } + + /** + * Appends the character sequence for "bold". + * @return this + */ + public ChatBuilder bold() { + return append("**"); + } + + /** + * Wraps text in "bold" formatting. + * @param text the text to wrap + * @return this + */ + public ChatBuilder bold(String text) { + return bold().append(text).bold(); + } + + /** + * Appends the character sequence for "code". + * @return this + */ + public ChatBuilder code() { + return append('`'); + } + + /** + * Wraps text in "code" formatting. + * @param text the text to wrap + * @return this + */ + public ChatBuilder code(String text) { + return code().append(text).code(); + } + + /** + * Appends the character sequence for "italic". + * @return this + */ + public ChatBuilder italic() { + return append('*'); + } + + /** + * Wraps text in "italic" formatting. + * @param text the text to wrap + * @return this + */ + public ChatBuilder italic(String text) { + return italic().append(text).italic(); + } + + /** + * Appends a clickable link. + * @param display the display text + * @param url the URL + * @return this + */ + public ChatBuilder link(String display, String url) { + return link(display, url, null); + } + + /** + * Appends a clickable link. + * @param display the display text + * @param url the URL + * @param title the link title or null/empty for no title + * @return this + */ + public ChatBuilder link(String display, String url, String title) { + append('[').append(display.trim()).append("](").append(url.trim()); + if (title != null && !title.isEmpty()) { + append(" \"").append(title.trim()).append('"'); + } + return append(')'); + } + + /** + * Appends a newline character. + * @return this + */ + public ChatBuilder nl() { + return append('\n'); + } + + /** + * Appends the "reply to message" syntax. + * @param message the message to reply to + * @return this + */ + public ChatBuilder reply(ChatMessage message) { + //TODO return append(':').append(message.getMessageId() + "").append(' '); + return append('@').append(message.getUsername()).append(' '); + } + + /** + * Appends the character sequence for "strike through". + * @return this + */ + public ChatBuilder strike() { + return append("---"); + } + + /** + * Wraps text in "strike through" formatting. + * @param text the text to wrap + * @return this + */ + public ChatBuilder strike(String text) { + return strike().append(text).strike(); + } + + /** + * Appends a tag. + * @param tag the tag name + * @return this + */ + public ChatBuilder tag(String tag) { + return append("[tag:").append(tag).append(']'); + } + + /** + * Appends a raw character. + * @param c the character to append + * @return this + */ + public ChatBuilder append(char c) { + sb.append(c); + return this; + } + + /** + * Appends a raw string. + * @param text the string to append + * @return this + */ + public ChatBuilder append(CharSequence text) { + sb.append(text); + return this; + } + + @Override + public int length() { + return sb.length(); + } + + @Override + public char charAt(int index) { + return sb.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return sb.subSequence(start, end); + } + + @Override + public String toString() { + return sb.toString(); + } +} diff --git a/src/main/java/com/gmail/inverseconduit/utils/DocumentWrapper.java b/src/main/java/com/gmail/inverseconduit/utils/DocumentWrapper.java new file mode 100644 index 0000000..298154d --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/utils/DocumentWrapper.java @@ -0,0 +1,107 @@ +package com.gmail.inverseconduit.utils; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Wraps an XML {@link Node} object, providing utility functionality. + * @author Michael Angstadt + */ +public class DocumentWrapper { + private final Node root; + private final XPath xpath; + + /** + * @param root the node to wrap + */ + public DocumentWrapper(Node root) { + this.root = root; + xpath = XPathFactory.newInstance().newXPath(); + } + + /** + * Queries the DOM for a specific element. + * @param query the xpath query + * @return the element or null if not found + */ + public Element element(String query) { + return element(query, root); + } + + /** + * Queries the DOM for a specific element. + * @param query the xpath query + * @param from the node to look inside of + * @return the element or null if not found + */ + public Element element(String query, Node from) { + try { + return (Element) xpath.evaluate(query, from, XPathConstants.NODE); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + /** + * Queries the DOM for multiple elements. + * @param query the xpath query + * @return the elements + */ + public List elements(String query) { + return elements(query, root); + } + + /** + * Queries the DOM for multiple elements. + * @param query the xpath query + * @param from the node to look inside of + * @return the elements + */ + public List elements(String query, Node from) { + NodeList list; + try { + list = (NodeList) xpath.evaluate(query, from, XPathConstants.NODESET); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } + + List elements = new ArrayList<>(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + elements.add((Element) list.item(i)); + } + return elements; + } + + @Override + public String toString() { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + StringWriter sw = new StringWriter(); + DOMSource source = new DOMSource(root); + StreamResult result = new StreamResult(sw); + transformer.transform(source, result); + return sw.toString(); + } catch (TransformerException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/CommandTextParserTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/CommandTextParserTest.java new file mode 100644 index 0000000..4a7982d --- /dev/null +++ b/src/test/java/com/gmail/inverseconduit/javadoc/CommandTextParserTest.java @@ -0,0 +1,123 @@ +package com.gmail.inverseconduit.javadoc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; + +import org.junit.Test; + +import com.gmail.inverseconduit.javadoc.JavaDocAccessor.CommandTextParser; + +/** + * @author Michael Angstadt + */ +public class CommandTextParserTest { + @Test + public void onMessage() { + CommandTextParser parser = new CommandTextParser("java.lang.string"); + assertEquals("java.lang.string", parser.getClassName()); + assertNull(parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(*)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf()"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList(), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("int"), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int[])"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("int[]"), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int...)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("int..."), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int, int)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("int", "int"), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int, int) 2"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(2, parser.getParagraph()); + assertEquals(Arrays.asList("int", "int"), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string#indexOf(int,int)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("indexOf", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("int", "int"), parser.getParameters()); + + parser = new CommandTextParser("string()"); + assertEquals("string", parser.getClassName()); + assertEquals("string", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList(), parser.getParameters()); + + parser = new CommandTextParser("string(string)"); + assertEquals("string", parser.getClassName()); + assertEquals("string", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("string"), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string()"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("string", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList(), parser.getParameters()); + + parser = new CommandTextParser("java.lang.string(string)"); + assertEquals("java.lang.string", parser.getClassName()); + assertEquals("string", parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertEquals(Arrays.asList("string"), parser.getParameters()); + } + + @Test + public void onMessage_invalid_paragraph() { + CommandTextParser parser = new CommandTextParser("java.lang.string foo"); + assertEquals("java.lang.string", parser.getClassName()); + assertNull(parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + + parser = new CommandTextParser("java.lang.string -1"); + assertEquals("java.lang.string", parser.getClassName()); + assertNull(parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + + parser = new CommandTextParser("java.lang.string 1.2"); + assertEquals("java.lang.string", parser.getClassName()); + assertNull(parser.getMethodName()); + assertEquals(1, parser.getParagraph()); + assertNull(parser.getParameters()); + } +} diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/Java8PageParserTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/Java8PageParserTest.java deleted file mode 100644 index 2196595..0000000 --- a/src/test/java/com/gmail/inverseconduit/javadoc/Java8PageParserTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.junit.Test; - -/** - * @author Michael Angstadt - */ -public class Java8PageParserTest { - - @Test - public void getAllClasses() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("java8-allclasses-frame.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - Java8PageParser parser = new Java8PageParser(); - List actual = parser.parseClassNames(document); - //@formatter:off - List expected = Arrays.asList( - "java.awt.List", - "java.lang.String", - "java.util.List", - "java.util.Map.Entry" - ); - //@formatter:on - assertEquals(expected, actual); - } - - @Test - public void getClassInfo() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("String.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - Java8PageParser parser = new Java8PageParser(); - ClassInfo info = parser.parseClassPage(document, "java.lang.String"); - assertEquals("java.lang.String", info.getFullName()); - assertEquals("https://docs.oracle.com/javase/8/docs/api/?java/lang/String.html", info.getUrl()); - - assertEquals(Arrays.asList("public", "final", "class"), info.getModifiers()); - assertFalse(info.isDeprecated()); - - //@formatter:off - assertEquals( - "The `String` class represents character strings.\n" + - " `code` text \n" + - " **bold** text\n" + - " **bold** text\n" + - " *italic* text\n" + - " *italic* text\n" + - " \\*asterisks\\*\n" + - " \\_underscores\\_\n" + - " \\[brackets\\]\n" + - " [Google Search](http://www.google.com \"with title\")\n" + - " [Bing Search](http://www.bing.com)\n" + - " Because String objects are immutable they can be shared. For example: \n" + - "\n" + - "\r\n" + - " String str = \"abc\";\r\n" + - "\n" + - "\n" + - " is equivalent to: \n" + - "\n" + - "\r\n" + - " char data[] = {'a', 'b', 'c'};\r\n" + - " String str = new String(data);\r\n" + - "\n" + - " \n" + - "ignore me", info.getDescription()); - //@formatter:on - } - - @Test - public void getClassInfo_annotation() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("SuppressWarnings.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - Java8PageParser parser = new Java8PageParser(); - ClassInfo info = parser.parseClassPage(document, "java.lang.SuppressWarnings"); - assertEquals("java.lang.SuppressWarnings", info.getFullName()); - assertEquals("https://docs.oracle.com/javase/8/docs/api/?java/lang/SuppressWarnings.html", info.getUrl()); - - assertEquals(Arrays.asList("public", "@interface"), info.getModifiers()); - assertFalse(info.isDeprecated()); - assertNotNull(info.getDescription()); - } - - @Test - public void getClassInfo_deprecated() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("StringBufferInputStream.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - Java8PageParser parser = new Java8PageParser(); - ClassInfo info = parser.parseClassPage(document, "java.io.StringBufferInputStream"); - assertEquals("java.io.StringBufferInputStream", info.getFullName()); - assertEquals("https://docs.oracle.com/javase/8/docs/api/?java/io/StringBufferInputStream.html", info.getUrl()); - - assertEquals(Arrays.asList("public", "class"), info.getModifiers()); - assertTrue(info.isDeprecated()); - System.out.println((int) info.getDescription().charAt(12)); - System.out.println((int) info.getDescription().charAt(13)); - assertNotNull(info.getDescription()); - } -} diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java index 23b0ef6..c3b27b6 100644 --- a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java +++ b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java @@ -1,139 +1,179 @@ package com.gmail.inverseconduit.javadoc; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.LogManager; +import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; /** * @author Michael Angstadt */ public class JavadocDaoTest { - private final JavadocDao dao = new JavadocDao(); + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final Path root = Paths.get("src", "test", "resources", "com", "gmail", "inverseconduit", "javadoc"); + private final JavadocDao dao; { try { - dao.addJavadocApi(new JavadocLibrary(null, null) { - @Override - public List getAllClassNames() throws IOException { - //@formatter:off - return Arrays.asList( - "javax.management.Attribute", - "javax.naming.directory.Attribute", - "java.lang.String" - ); - //@formatter:on - } - - @Override - public ClassInfo getClassInfo(String className) throws IOException { - if (className.startsWith("java.")) { - return new ClassInfo(className, "description - " + className, null, Collections.emptyList(), false); - } - return null; - } - }); - dao.addJavadocApi(new JavadocLibrary(null, null) { - @Override - public List getAllClassNames() throws IOException { - //@formatter:off - return Arrays.asList( - "org.jsoup.nodes.Attribute" - ); - //@formatter:on - } - - @Override - public ClassInfo getClassInfo(String className) throws IOException { - if (className.startsWith("org.")) { - return new ClassInfo(className, "description - " + className, null, Collections.emptyList(), false); - } - return null; - } - }); + dao = new JavadocDao(root); } catch (IOException e) { - //not thrown + throw new RuntimeException(e); } } + @BeforeClass + public static void beforeClass() { + //turn off logging + LogManager.getLogManager().reset(); + } + @Test public void simpleName_single_match() throws Exception { - ClassInfo info = dao.getClassInfo("String"); - assertEquals("java.lang.String", info.getFullName()); - assertEquals("description - java.lang.String", info.getDescription()); + ClassInfo info = dao.getClassInfo("Collection"); + assertEquals("java.util.Collection", info.getName().getFull()); } @Test public void simpleName_no_match() throws Exception { ClassInfo info = dao.getClassInfo("FooBar"); assertNull(info); - - info = dao.getClassInfo("freemarker.FooBar"); - assertNull(info); } @Test public void simpleName_case_insensitive() throws Exception { - ClassInfo info = dao.getClassInfo("string"); - assertEquals("java.lang.String", info.getFullName()); - assertEquals("description - java.lang.String", info.getDescription()); + ClassInfo info = dao.getClassInfo("collection"); + assertEquals("java.util.Collection", info.getName().getFull()); } @Test public void simpleName_multiple_matches() throws Exception { try { - ClassInfo info = dao.getClassInfo("Attribute"); - fail(info.getFullName()); + dao.getClassInfo("List"); + fail(); } catch (MultipleClassesFoundException e) { Set actual = new HashSet<>(e.getClasses()); - Set expected = new HashSet<>(Arrays.asList("javax.management.Attribute", "javax.naming.directory.Attribute", "org.jsoup.nodes.Attribute")); + Set expected = new HashSet<>(Arrays.asList("java.awt.List", "java.util.List")); assertEquals(expected, actual); } } @Test public void fullName() throws Exception { - ClassInfo info = dao.getClassInfo("org.jsoup.nodes.Attribute"); - assertEquals("org.jsoup.nodes.Attribute", info.getFullName()); - assertEquals("description - org.jsoup.nodes.Attribute", info.getDescription()); + ClassInfo info = dao.getClassInfo("java.util.List"); + assertEquals("java.util.List", info.getName().getFull()); + } + + @Test + public void fullName_case_insensitive() throws Exception { + ClassInfo info = dao.getClassInfo("java.util.list"); + assertEquals("java.util.List", info.getName().getFull()); + } + + @Test + public void directory_watcher_ignore_non_zip_files() throws Exception { + Path dir = temporaryFolder.getRoot().toPath(); + JavadocDao dao = new JavadocDao(dir); + + assertNull(dao.getClassInfo("java.util.List")); + + Path source = root.resolve("LibraryZipFileTest.zip"); + Path dest = dir.resolve("LibraryZipFileTest.txt"); + Files.copy(source, dest); + Thread.sleep(1000); + assertNull(dao.getClassInfo("java.util.List")); } @Test - public void cache() throws Exception { - JavadocLibrary spy = spy(new JavadocLibrary(null, null) { - @Override - public List getAllClassNames() throws IOException { - //@formatter:off - return Arrays.asList( - "java.lang.String" - ); - //@formatter:on - } - - @Override - public ClassInfo getClassInfo(String className) throws IOException { - if (className.startsWith("java.")) { - return new ClassInfo(className, "description - " + className, null, Collections.emptyList(), false); - } - return null; - } - }); - - JavadocDao dao = new JavadocDao(); - dao.addJavadocApi(spy); - - dao.getClassInfo("String"); - dao.getClassInfo("string"); - verify(spy, times(1)).getClassInfo("java.lang.String"); + public void directory_watcher_add() throws Exception { + Path dir = temporaryFolder.getRoot().toPath(); + JavadocDao dao = new JavadocDao(dir); + + assertNull(dao.getClassInfo("java.util.List")); + + Path source = root.resolve("LibraryZipFileTest.zip"); + Path dest = dir.resolve("LibraryZipFileTest.zip"); + Files.copy(source, dest); + + //wait for the WatchService to pick up the file + //this is really slow on Macs, see: http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else + long start = System.currentTimeMillis(); + ClassInfo info = null; + while (info == null && (System.currentTimeMillis() - start) < TimeUnit.SECONDS.toMillis(5)) { + Thread.sleep(200); + info = dao.getClassInfo("java.util.List"); + } + assertNotNull(info); + } + + @Test + public void directory_watcher_remove() throws Exception { + Path dir = temporaryFolder.getRoot().toPath(); + Path source = root.resolve("LibraryZipFileTest.zip"); + Path dest = dir.resolve("LibraryZipFileTest.zip"); + Files.copy(source, dest); + + JavadocDao dao = new JavadocDao(dir); + + ClassInfo info = dao.getClassInfo("java.util.List"); + assertNotNull(info); + + source = dir.resolve("LibraryZipFileTest.zip"); + Files.delete(source); + + //wait for the WatchService to pick up the deleted file + //this is really slow on Macs, see: http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else + long start = System.currentTimeMillis(); + while (info != null && (System.currentTimeMillis() - start) < TimeUnit.SECONDS.toMillis(5)) { + Thread.sleep(200); + info = dao.getClassInfo("java.util.List"); + } + assertNull(info); + } + + @Test + public void directory_watcher_modified() throws Exception { + Path dir = temporaryFolder.getRoot().toPath(); + Path source = root.resolve("LibraryZipFileTest.zip"); + Path dest = dir.resolve("LibraryZipFileTest.zip"); + Files.copy(source, dest); + + Thread.sleep(1500); //wait a bit before modifying the file so the timestamp is significantly different (for Macs) + + JavadocDao dao = new JavadocDao(dir); + + ClassInfo info = dao.getClassInfo("java.util.List"); + assertNotNull(info); + + try (FileSystem fs = FileSystems.newFileSystem(dest, null)) { + Path path = fs.getPath("java.util.List.xml"); + Files.delete(path); + } + + //wait for the WatchService to pick up the change + //this is really slow on Macs, see: http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else + long start = System.currentTimeMillis(); + while (info != null && (System.currentTimeMillis() - start) < TimeUnit.SECONDS.toMillis(5)) { + Thread.sleep(200); + info = dao.getClassInfo("java.util.List"); + } + assertNull(info); } } diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/JsoupPageParserTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/JsoupPageParserTest.java deleted file mode 100644 index fa1c87c..0000000 --- a/src/test/java/com/gmail/inverseconduit/javadoc/JsoupPageParserTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.junit.Test; - -/** - * @author Michael Angstadt - */ -public class JsoupPageParserTest { - @Test - public void getAllClasses() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("jsoup-allclasses-frame.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - JsoupPageParser parser = new JsoupPageParser(); - List actual = parser.parseClassNames(document); - //@formatter:off - List expected = Arrays.asList( - "org.jsoup.nodes.Attribute", - "org.jsoup.nodes.Attributes", - "org.jsoup.nodes.Comment", - "org.jsoup.Connection", - "org.jsoup.Connection.Base" - ); - //@formatter:on - assertEquals(expected, actual); - } - - @Test - public void getClassInfo() throws Exception { - Document document; - try (InputStream in = getClass().getResourceAsStream("Attribute.html")) { - document = Jsoup.parse(in, "UTF-8", ""); - } - - JsoupPageParser parser = new JsoupPageParser(); - ClassInfo info = parser.parseClassPage(document, "org.jsoup.nodes.Attribute"); - assertEquals("org.jsoup.nodes.Attribute", info.getFullName()); - assertEquals(Arrays.asList("public", "class"), info.getModifiers()); - assertFalse(info.isDeprecated()); - assertEquals("http://jsoup.org/apidocs/?org/jsoup/nodes/Attribute.html", info.getUrl()); - assertEquals("A single key + value attribute. Keys are trimmed and normalised to lower-case.", info.getDescription()); - } -} diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java new file mode 100644 index 0000000..b68e4b5 --- /dev/null +++ b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java @@ -0,0 +1,87 @@ +package com.gmail.inverseconduit.javadoc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; + +/** + * @author Michael Angstadt + */ +public class LibraryZipFileTest { + private final Path root = Paths.get("src", "test", "resources", "com", "gmail", "inverseconduit", "javadoc"); + private final LibraryZipFile zip; + { + Path file = root.resolve(getClass().getSimpleName() + ".zip"); + try { + zip = new LibraryZipFile(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void info_without_file() throws Exception { + Path file = root.resolve(getClass().getSimpleName() + "-no-info.zip"); + LibraryZipFile zip = new LibraryZipFile(file); + assertNull(zip.getName()); + assertNull(zip.getBaseUrl()); + } + + @Test + public void info_without_attributes() throws Exception { + Path file = root.resolve(getClass().getSimpleName() + "-no-attributes.zip"); + LibraryZipFile zip = new LibraryZipFile(file); + assertNull(zip.getName()); + assertNull(zip.getBaseUrl()); + } + + @Test + public void info() { + assertEquals("Java", zip.getName()); + assertEquals("8", zip.getVersion()); + assertEquals("https://docs.oracle.com/javase/8/docs/api/", zip.getBaseUrl()); + assertEquals("http://java.oracle.com", zip.getProjectUrl()); + } + + @Test + public void getClasses() throws Exception { + Iterator it = zip.getClasses(); + Set actual = new HashSet<>(); + while (it.hasNext()) { + actual.add(it.next().getFull()); + } + + //@formatter:off + Set expected = new HashSet<>(Arrays.asList( + "java.lang.Object", + "java.awt.List", + "java.util.List", + "java.util.Collection" + )); + //@formatter:on + + assertEquals(expected, actual); + } + + @Test + public void getClassInfo_not_found() throws Exception { + ClassInfo info = zip.getClassInfo("java.lang.Foo"); + assertNull(info); + } + + @Test + public void getClassInfo() throws Exception { + ClassInfo info = zip.getClassInfo("java.lang.Object"); + assertEquals("java.lang.Object", info.getName().getFull()); + assertEquals("Object", info.getName().getSimple()); + } +} diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/ZipPageLoaderTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/ZipPageLoaderTest.java deleted file mode 100644 index 8376f0d..0000000 --- a/src/test/java/com/gmail/inverseconduit/javadoc/ZipPageLoaderTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.gmail.inverseconduit.javadoc; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.apache.commons.io.IOUtils; -import org.junit.Test; - -/** - * @author Michael Angstadt - */ -public class ZipPageLoaderTest { - private final Path folder = Paths.get("src", "test", "resources", "com", "gmail", "inverseconduit", "javadoc"); - private final Path file = folder.resolve("javadoc-file.zip"); - private final PageLoader loader; - - { - try { - loader = new ZipPageLoader(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - public void getAllClassesFiles() throws Exception { - String expected; - try (FileSystem fs = FileSystems.newFileSystem(file, null)) { - Path allClassesFile = fs.getPath("/allclasses-frame.html"); - expected = new String(Files.readAllBytes(allClassesFile)); - } - - String actual; - try (InputStream in = loader.getAllClassesFile()) { - actual = IOUtils.toString(in); - } - - assertEquals(expected, actual); - } - - @Test(expected = IllegalArgumentException.class) - public void getAllClassesFiles_no_allclasses_file() throws Exception { - new ZipPageLoader(folder.resolve("javadoc-without-allclasses-frame.zip")); - } - - @Test(expected = FileSystemNotFoundException.class) - public void getAllClassesFiles_does_not_exist() throws Exception { - new ZipPageLoader(Paths.get("foobar.zip")); - - } - - @Test - public void getClassPage() throws Exception { - String expected; - try (FileSystem fs = FileSystems.newFileSystem(file, null)) { - Path allClassesFile = fs.getPath("/java/lang/String.html"); - expected = new String(Files.readAllBytes(allClassesFile)); - } - - String actual; - try (InputStream in = loader.getClassPage("java.lang.String")) { - actual = IOUtils.toString(in); - } - - assertEquals(expected, actual); - } - - @Test - public void getClassPage_does_not_exist() throws Exception { - InputStream in = loader.getClassPage("java.util.FooBar"); - assertNull(in); - } -} diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/Attribute.html b/src/test/resources/com/gmail/inverseconduit/javadoc/Attribute.html deleted file mode 100644 index 3a051b6..0000000 --- a/src/test/resources/com/gmail/inverseconduit/javadoc/Attribute.html +++ /dev/null @@ -1,562 +0,0 @@ - - - - - - - -Attribute (jsoup 1.8.1 API) - - - - - - - - - - - - -


- - - - - - - - - - - - - - - - - - - -
- -
- - - -
- -

- -org.jsoup.nodes -
-Class Attribute

-
-java.lang.Object
-  extended by org.jsoup.nodes.Attribute
-
-
-
All Implemented Interfaces:
Cloneable, Map.Entry<String,String>
-
-
-
-
public class Attribute
extends Object
implements Map.Entry<String,String>, Cloneable
- - -

-A single key + value attribute. Keys are trimmed and normalised to lower-case. -

- -

-

-
Author:
-
Jonathan Hedley, jonathan@hedley.net
-
-
- -

- - - - - - - - - - - -
-Constructor Summary
Attribute(String key, - String value) - -
-          Create a new attribute from unencoded (raw) key and value.
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Method Summary
- Attributeclone() - -
-           
-static AttributecreateFromEncoded(String unencodedKey, - String encodedValue) - -
-          Create a new Attribute from an unencoded key and a HTML attribute encoded value.
- booleanequals(Object o) - -
-           
- StringgetKey() - -
-          Get the attribute key.
- StringgetValue() - -
-          Get the attribute value.
- inthashCode() - -
-           
- Stringhtml() - -
-          Get the HTML representation of this attribute; e.g.
-protected  voidhtml(StringBuilder accum, - Document.OutputSettings out) - -
-           
-protected  booleanisDataAttribute() - -
-           
- voidsetKey(String key) - -
-          Set the attribute key.
- StringsetValue(String value) - -
-          Set the attribute value.
-protected  booleanshouldCollapseAttribute(Document.OutputSettings out) - -
-          Collapsible if it's a boolean attribute and value is empty or same as name
- StringtoString() - -
-          Get the string representation of this attribute, implemented as html().
- - - - - - - -
Methods inherited from class java.lang.Object
finalize, getClass, notify, notifyAll, wait, wait, wait
-  -

- - - - - - - - -
-Constructor Detail
- -

-Attribute

-
-public Attribute(String key,
-                 String value)
-
-
Create a new attribute from unencoded (raw) key and value. -

-

-
Parameters:
key - attribute key
value - attribute value
See Also:
createFromEncoded(java.lang.String, java.lang.String)
-
- - - - - - - - -
-Method Detail
- -

-getKey

-
-public String getKey()
-
-
Get the attribute key. -

-

-
Specified by:
getKey in interface Map.Entry<String,String>
-
-
- -
Returns:
the attribute key
-
-
-
- -

-setKey

-
-public void setKey(String key)
-
-
Set the attribute key. Gets normalised as per the constructor method. -

-

-
-
-
-
Parameters:
key - the new key; must not be null
-
-
-
- -

-getValue

-
-public String getValue()
-
-
Get the attribute value. -

-

-
Specified by:
getValue in interface Map.Entry<String,String>
-
-
- -
Returns:
the attribute value
-
-
-
- -

-setValue

-
-public String setValue(String value)
-
-
Set the attribute value. -

-

-
Specified by:
setValue in interface Map.Entry<String,String>
-
-
-
Parameters:
value - the new attribute value; must not be null
-
-
-
- -

-html

-
-public String html()
-
-
Get the HTML representation of this attribute; e.g. href="index.html". -

-

-
-
-
- -
Returns:
HTML
-
-
-
- -

-html

-
-protected void html(StringBuilder accum,
-                    Document.OutputSettings out)
-
-
-
-
-
-
-
-
-
- -

-toString

-
-public String toString()
-
-
Get the string representation of this attribute, implemented as html(). -

-

-
Overrides:
toString in class Object
-
-
- -
Returns:
string
-
-
-
- -

-createFromEncoded

-
-public static Attribute createFromEncoded(String unencodedKey,
-                                          String encodedValue)
-
-
Create a new Attribute from an unencoded key and a HTML attribute encoded value. -

-

-
-
-
-
Parameters:
unencodedKey - assumes the key is not encoded, as can be only run of simple \w chars.
encodedValue - HTML attribute encoded value -
Returns:
attribute
-
-
-
- -

-isDataAttribute

-
-protected boolean isDataAttribute()
-
-
-
-
-
-
-
-
-
- -

-shouldCollapseAttribute

-
-protected final boolean shouldCollapseAttribute(Document.OutputSettings out)
-
-
Collapsible if it's a boolean attribute and value is empty or same as name -

-

-
-
-
-
-
-
-
- -

-equals

-
-public boolean equals(Object o)
-
-
-
Specified by:
equals in interface Map.Entry<String,String>
Overrides:
equals in class Object
-
-
-
-
-
-
- -

-hashCode

-
-public int hashCode()
-
-
-
Specified by:
hashCode in interface Map.Entry<String,String>
Overrides:
hashCode in class Object
-
-
-
-
-
-
- -

-clone

-
-public Attribute clone()
-
-
-
Overrides:
clone in class Object
-
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - -
- -
- - - -
-Copyright © 2009-2014 Jonathan Hedley. All Rights Reserved. - - diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-attributes.zip b/src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-attributes.zip new file mode 100644 index 0000000000000000000000000000000000000000..becda5e153c41dbb536187550b081d3ade6d6a0a GIT binary patch literal 17834 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 V1bDNufz+@9p)Df=L%0iw2LQ+y8@B)e literal 0 HcmV?d00001 diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-info.zip b/src/test/resources/com/gmail/inverseconduit/javadoc/LibraryZipFileTest-no-info.zip new file mode 100644 index 0000000000000000000000000000000000000000..23a683b53b9d32a5b239d137d443828576124e14 GIT binary patch literal 17679 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 bX)zQ3iM5zn9GnLu<=8*rEC4{Z>A~$kw0cFx literal 0 HcmV?d00001 diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/String.html b/src/test/resources/com/gmail/inverseconduit/javadoc/String.html deleted file mode 100644 index 1408e45..0000000 --- a/src/test/resources/com/gmail/inverseconduit/javadoc/String.html +++ /dev/null @@ -1,3573 +0,0 @@ - - - - - -String (Java Platform SE 8 ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
compact1, compact2, compact3
-
java.lang
-

Class String

-
-
- -
- -
-
-
    -
  • - -
      -
    • - - -

      Field Summary

      - - - - - - - - - - -
      Fields 
      Modifier and TypeField and Description
      static Comparator<String>CASE_INSENSITIVE_ORDER -
      A Comparator that orders String objects as by - compareToIgnoreCase.
      -
      -
    • -
    - -
      -
    • - - -

      Constructor Summary

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      Constructors 
      Constructor and Description
      String() -
      Initializes a newly created String object so that it represents - an empty character sequence.
      -
      String(byte[] bytes) -
      Constructs a new String by decoding the specified array of bytes - using the platform's default charset.
      -
      String(byte[] bytes, - Charset charset) -
      Constructs a new String by decoding the specified array of - bytes using the specified charset.
      -
      String(byte[] ascii, - int hibyte) -
      Deprecated.  -
      This method does not properly convert bytes into - characters. As of JDK 1.1, the preferred way to do this is via the - String constructors that take a Charset, charset name, or that use the platform's - default charset.
      -
      -
      String(byte[] bytes, - int offset, - int length) -
      Constructs a new String by decoding the specified subarray of - bytes using the platform's default charset.
      -
      String(byte[] bytes, - int offset, - int length, - Charset charset) -
      Constructs a new String by decoding the specified subarray of - bytes using the specified charset.
      -
      String(byte[] ascii, - int hibyte, - int offset, - int count) -
      Deprecated.  -
      This method does not properly convert bytes into characters. - As of JDK 1.1, the preferred way to do this is via the - String constructors that take a Charset, charset name, or that use the platform's - default charset.
      -
      -
      String(byte[] bytes, - int offset, - int length, - String charsetName) -
      Constructs a new String by decoding the specified subarray of - bytes using the specified charset.
      -
      String(byte[] bytes, - String charsetName) -
      Constructs a new String by decoding the specified array of bytes - using the specified charset.
      -
      String(char[] value) -
      Allocates a new String so that it represents the sequence of - characters currently contained in the character array argument.
      -
      String(char[] value, - int offset, - int count) -
      Allocates a new String that contains characters from a subarray - of the character array argument.
      -
      String(int[] codePoints, - int offset, - int count) -
      Allocates a new String that contains characters from a subarray - of the Unicode code point array - argument.
      -
      String(String original) -
      Initializes a newly created String object so that it represents - the same sequence of characters as the argument; in other words, the - newly created string is a copy of the argument string.
      -
      String(StringBuffer buffer) -
      Allocates a new string that contains the sequence of characters - currently contained in the string buffer argument.
      -
      String(StringBuilder builder) -
      Allocates a new string that contains the sequence of characters - currently contained in the string builder argument.
      -
      -
    • -
    - -
      -
    • - - -

      Method Summary

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      All Methods Static Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethod and Description
      charcharAt(int index) -
      Returns the char value at the - specified index.
      -
      intcodePointAt(int index) -
      Returns the character (Unicode code point) at the specified - index.
      -
      intcodePointBefore(int index) -
      Returns the character (Unicode code point) before the specified - index.
      -
      intcodePointCount(int beginIndex, - int endIndex) -
      Returns the number of Unicode code points in the specified text - range of this String.
      -
      intcompareTo(String anotherString) -
      Compares two strings lexicographically.
      -
      intcompareToIgnoreCase(String str) -
      Compares two strings lexicographically, ignoring case - differences.
      -
      Stringconcat(String str) -
      Concatenates the specified string to the end of this string.
      -
      booleancontains(CharSequence s) -
      Returns true if and only if this string contains the specified - sequence of char values.
      -
      booleancontentEquals(CharSequence cs) -
      Compares this string to the specified CharSequence.
      -
      booleancontentEquals(StringBuffer sb) -
      Compares this string to the specified StringBuffer.
      -
      static StringcopyValueOf(char[] data) -
      Equivalent to valueOf(char[]).
      -
      static StringcopyValueOf(char[] data, - int offset, - int count) - -
      booleanendsWith(String suffix) -
      Tests if this string ends with the specified suffix.
      -
      booleanequals(Object anObject) -
      Compares this string to the specified object.
      -
      booleanequalsIgnoreCase(String anotherString) -
      Compares this String to another String, ignoring case - considerations.
      -
      static Stringformat(Locale l, - String format, - Object... args) -
      Returns a formatted string using the specified locale, format string, - and arguments.
      -
      static Stringformat(String format, - Object... args) -
      Returns a formatted string using the specified format string and - arguments.
      -
      byte[]getBytes() -
      Encodes this String into a sequence of bytes using the - platform's default charset, storing the result into a new byte array.
      -
      byte[]getBytes(Charset charset) -
      Encodes this String into a sequence of bytes using the given - charset, storing the result into a - new byte array.
      -
      voidgetBytes(int srcBegin, - int srcEnd, - byte[] dst, - int dstBegin) -
      Deprecated.  -
      This method does not properly convert characters into - bytes. As of JDK 1.1, the preferred way to do this is via the - getBytes() method, which uses the platform's default charset.
      -
      -
      byte[]getBytes(String charsetName) -
      Encodes this String into a sequence of bytes using the named - charset, storing the result into a new byte array.
      -
      voidgetChars(int srcBegin, - int srcEnd, - char[] dst, - int dstBegin) -
      Copies characters from this string into the destination character - array.
      -
      inthashCode() -
      Returns a hash code for this string.
      -
      intindexOf(int ch) -
      Returns the index within this string of the first occurrence of - the specified character.
      -
      intindexOf(int ch, - int fromIndex) -
      Returns the index within this string of the first occurrence of the - specified character, starting the search at the specified index.
      -
      intindexOf(String str) -
      Returns the index within this string of the first occurrence of the - specified substring.
      -
      intindexOf(String str, - int fromIndex) -
      Returns the index within this string of the first occurrence of the - specified substring, starting at the specified index.
      -
      Stringintern() -
      Returns a canonical representation for the string object.
      -
      booleanisEmpty() -
      Returns true if, and only if, length() is 0.
      -
      static Stringjoin(CharSequence delimiter, - CharSequence... elements) -
      Returns a new String composed of copies of the - CharSequence elements joined together with a copy of - the specified delimiter.
      -
      static Stringjoin(CharSequence delimiter, - Iterable<? extends CharSequence> elements) -
      Returns a new String composed of copies of the - CharSequence elements joined together with a copy of the - specified delimiter.
      -
      intlastIndexOf(int ch) -
      Returns the index within this string of the last occurrence of - the specified character.
      -
      intlastIndexOf(int ch, - int fromIndex) -
      Returns the index within this string of the last occurrence of - the specified character, searching backward starting at the - specified index.
      -
      intlastIndexOf(String str) -
      Returns the index within this string of the last occurrence of the - specified substring.
      -
      intlastIndexOf(String str, - int fromIndex) -
      Returns the index within this string of the last occurrence of the - specified substring, searching backward starting at the specified index.
      -
      intlength() -
      Returns the length of this string.
      -
      booleanmatches(String regex) -
      Tells whether or not this string matches the given regular expression.
      -
      intoffsetByCodePoints(int index, - int codePointOffset) -
      Returns the index within this String that is - offset from the given index by - codePointOffset code points.
      -
      booleanregionMatches(boolean ignoreCase, - int toffset, - String other, - int ooffset, - int len) -
      Tests if two string regions are equal.
      -
      booleanregionMatches(int toffset, - String other, - int ooffset, - int len) -
      Tests if two string regions are equal.
      -
      Stringreplace(char oldChar, - char newChar) -
      Returns a string resulting from replacing all occurrences of - oldChar in this string with newChar.
      -
      Stringreplace(CharSequence target, - CharSequence replacement) -
      Replaces each substring of this string that matches the literal target - sequence with the specified literal replacement sequence.
      -
      StringreplaceAll(String regex, - String replacement) -
      Replaces each substring of this string that matches the given regular expression with the - given replacement.
      -
      StringreplaceFirst(String regex, - String replacement) -
      Replaces the first substring of this string that matches the given regular expression with the - given replacement.
      -
      String[]split(String regex) -
      Splits this string around matches of the given regular expression.
      -
      String[]split(String regex, - int limit) -
      Splits this string around matches of the given - regular expression.
      -
      booleanstartsWith(String prefix) -
      Tests if this string starts with the specified prefix.
      -
      booleanstartsWith(String prefix, - int toffset) -
      Tests if the substring of this string beginning at the - specified index starts with the specified prefix.
      -
      CharSequencesubSequence(int beginIndex, - int endIndex) -
      Returns a character sequence that is a subsequence of this sequence.
      -
      Stringsubstring(int beginIndex) -
      Returns a string that is a substring of this string.
      -
      Stringsubstring(int beginIndex, - int endIndex) -
      Returns a string that is a substring of this string.
      -
      char[]toCharArray() -
      Converts this string to a new character array.
      -
      StringtoLowerCase() -
      Converts all of the characters in this String to lower - case using the rules of the default locale.
      -
      StringtoLowerCase(Locale locale) -
      Converts all of the characters in this String to lower - case using the rules of the given Locale.
      -
      StringtoString() -
      This object (which is already a string!) is itself returned.
      -
      StringtoUpperCase() -
      Converts all of the characters in this String to upper - case using the rules of the default locale.
      -
      StringtoUpperCase(Locale locale) -
      Converts all of the characters in this String to upper - case using the rules of the given Locale.
      -
      Stringtrim() -
      Returns a string whose value is this string, with any leading and trailing - whitespace removed.
      -
      static StringvalueOf(boolean b) -
      Returns the string representation of the boolean argument.
      -
      static StringvalueOf(char c) -
      Returns the string representation of the char - argument.
      -
      static StringvalueOf(char[] data) -
      Returns the string representation of the char array - argument.
      -
      static StringvalueOf(char[] data, - int offset, - int count) -
      Returns the string representation of a specific subarray of the - char array argument.
      -
      static StringvalueOf(double d) -
      Returns the string representation of the double argument.
      -
      static StringvalueOf(float f) -
      Returns the string representation of the float argument.
      -
      static StringvalueOf(int i) -
      Returns the string representation of the int argument.
      -
      static StringvalueOf(long l) -
      Returns the string representation of the long argument.
      -
      static StringvalueOf(Object obj) -
      Returns the string representation of the Object argument.
      -
      - - -
    • -
    -
  • -
-
-
-
    -
  • - -
      -
    • - - -

      Field Detail

      - - - -
        -
      • -

        CASE_INSENSITIVE_ORDER

        -
        public static final Comparator<String> CASE_INSENSITIVE_ORDER
        -
        A Comparator that orders String objects as by - compareToIgnoreCase. This comparator is serializable. -

        - Note that this Comparator does not take locale into account, - and will result in an unsatisfactory ordering for certain locales. - The java.text package provides Collators to allow - locale-sensitive ordering.

        -
        -
        Since:
        -
        1.2
        -
        See Also:
        -
        Collator.compare(String, String)
        -
        -
      • -
      -
    • -
    - -
      -
    • - - -

      Constructor Detail

      - - - -
        -
      • -

        String

        -
        public String()
        -
        Initializes a newly created String object so that it represents - an empty character sequence. Note that use of this constructor is - unnecessary since Strings are immutable.
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(String original)
        -
        Initializes a newly created String object so that it represents - the same sequence of characters as the argument; in other words, the - newly created string is a copy of the argument string. Unless an - explicit copy of original is needed, use of this constructor is - unnecessary since Strings are immutable.
        -
        -
        Parameters:
        -
        original - A String
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(char[] value)
        -
        Allocates a new String so that it represents the sequence of - characters currently contained in the character array argument. The - contents of the character array are copied; subsequent modification of - the character array does not affect the newly created string.
        -
        -
        Parameters:
        -
        value - The initial value of the string
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(char[] value,
        -              int offset,
        -              int count)
        -
        Allocates a new String that contains characters from a subarray - of the character array argument. The offset argument is the - index of the first character of the subarray and the count - argument specifies the length of the subarray. The contents of the - subarray are copied; subsequent modification of the character array does - not affect the newly created string.
        -
        -
        Parameters:
        -
        value - Array that is the source of characters
        -
        offset - The initial offset
        -
        count - The length
        -
        Throws:
        -
        IndexOutOfBoundsException - If the offset and count arguments index - characters outside the bounds of the value array
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(int[] codePoints,
        -              int offset,
        -              int count)
        -
        Allocates a new String that contains characters from a subarray - of the Unicode code point array - argument. The offset argument is the index of the first code - point of the subarray and the count argument specifies the - length of the subarray. The contents of the subarray are converted to - chars; subsequent modification of the int array does not - affect the newly created string.
        -
        -
        Parameters:
        -
        codePoints - Array that is the source of Unicode code points
        -
        offset - The initial offset
        -
        count - The length
        -
        Throws:
        -
        IllegalArgumentException - If any invalid Unicode code point is found in codePoints
        -
        IndexOutOfBoundsException - If the offset and count arguments index - characters outside the bounds of the codePoints array
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - - - - - - - - - -
        -
      • -

        String

        -
        public String(byte[] bytes,
        -              int offset,
        -              int length,
        -              String charsetName)
        -       throws UnsupportedEncodingException
        -
        Constructs a new String by decoding the specified subarray of - bytes using the specified charset. The length of the new String - is a function of the charset, and hence may not be equal to the length - of the subarray. - -

        The behavior of this constructor when the given bytes are not valid - in the given charset is unspecified. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        offset - The index of the first byte to decode
        -
        length - The number of bytes to decode
        -
        charsetName - The name of a supported charset
        -
        Throws:
        -
        UnsupportedEncodingException - If the named charset is not supported
        -
        IndexOutOfBoundsException - If the offset and length arguments index - characters outside the bounds of the bytes array
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(byte[] bytes,
        -              int offset,
        -              int length,
        -              Charset charset)
        -
        Constructs a new String by decoding the specified subarray of - bytes using the specified charset. - The length of the new String is a function of the charset, and - hence may not be equal to the length of the subarray. - -

        This method always replaces malformed-input and unmappable-character - sequences with this charset's default replacement string. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        offset - The index of the first byte to decode
        -
        length - The number of bytes to decode
        -
        charset - The charset to be used to - decode the bytes
        -
        Throws:
        -
        IndexOutOfBoundsException - If the offset and length arguments index - characters outside the bounds of the bytes array
        -
        Since:
        -
        1.6
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(byte[] bytes,
        -              String charsetName)
        -       throws UnsupportedEncodingException
        -
        Constructs a new String by decoding the specified array of bytes - using the specified charset. The - length of the new String is a function of the charset, and hence - may not be equal to the length of the byte array. - -

        The behavior of this constructor when the given bytes are not valid - in the given charset is unspecified. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        charsetName - The name of a supported charset
        -
        Throws:
        -
        UnsupportedEncodingException - If the named charset is not supported
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(byte[] bytes,
        -              Charset charset)
        -
        Constructs a new String by decoding the specified array of - bytes using the specified charset. - The length of the new String is a function of the charset, and - hence may not be equal to the length of the byte array. - -

        This method always replaces malformed-input and unmappable-character - sequences with this charset's default replacement string. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        charset - The charset to be used to - decode the bytes
        -
        Since:
        -
        1.6
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(byte[] bytes,
        -              int offset,
        -              int length)
        -
        Constructs a new String by decoding the specified subarray of - bytes using the platform's default charset. The length of the new - String is a function of the charset, and hence may not be equal - to the length of the subarray. - -

        The behavior of this constructor when the given bytes are not valid - in the default charset is unspecified. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        offset - The index of the first byte to decode
        -
        length - The number of bytes to decode
        -
        Throws:
        -
        IndexOutOfBoundsException - If the offset and the length arguments index - characters outside the bounds of the bytes array
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(byte[] bytes)
        -
        Constructs a new String by decoding the specified array of bytes - using the platform's default charset. The length of the new String is a function of the charset, and hence may not be equal to the - length of the byte array. - -

        The behavior of this constructor when the given bytes are not valid - in the default charset is unspecified. The CharsetDecoder class should be used when more control - over the decoding process is required.

        -
        -
        Parameters:
        -
        bytes - The bytes to be decoded into characters
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(StringBuffer buffer)
        -
        Allocates a new string that contains the sequence of characters - currently contained in the string buffer argument. The contents of the - string buffer are copied; subsequent modification of the string buffer - does not affect the newly created string.
        -
        -
        Parameters:
        -
        buffer - A StringBuffer
        -
        -
      • -
      - - - -
        -
      • -

        String

        -
        public String(StringBuilder builder)
        -
        Allocates a new string that contains the sequence of characters - currently contained in the string builder argument. The contents of the - string builder are copied; subsequent modification of the string builder - does not affect the newly created string. - -

        This constructor is provided to ease migration to StringBuilder. Obtaining a string from a string builder via the toString method is likely to run faster and is generally preferred.

        -
        -
        Parameters:
        -
        builder - A StringBuilder
        -
        Since:
        -
        1.5
        -
        -
      • -
      -
    • -
    - -
      -
    • - - -

      Method Detail

      - - - -
        -
      • -

        length

        -
        public int length()
        -
        Returns the length of this string. - The length is equal to the number of Unicode - code units in the string.
        -
        -
        Specified by:
        -
        length in interface CharSequence
        -
        Returns:
        -
        the length of the sequence of characters represented by this - object.
        -
        -
      • -
      - - - -
        -
      • -

        isEmpty

        -
        public boolean isEmpty()
        -
        Returns true if, and only if, length() is 0.
        -
        -
        Returns:
        -
        true if length() is 0, otherwise - false
        -
        Since:
        -
        1.6
        -
        -
      • -
      - - - -
        -
      • -

        charAt

        -
        public char charAt(int index)
        -
        Returns the char value at the - specified index. An index ranges from 0 to - length() - 1. The first char value of the sequence - is at index 0, the next at index 1, - and so on, as for array indexing. - -

        If the char value specified by the index is a - surrogate, the surrogate - value is returned.

        -
        -
        Specified by:
        -
        charAt in interface CharSequence
        -
        Parameters:
        -
        index - the index of the char value.
        -
        Returns:
        -
        the char value at the specified index of this string. - The first char value is at index 0.
        -
        Throws:
        -
        IndexOutOfBoundsException - if the index - argument is negative or not less than the length of this - string.
        -
        -
      • -
      - - - -
        -
      • -

        codePointAt

        -
        public int codePointAt(int index)
        -
        Returns the character (Unicode code point) at the specified - index. The index refers to char values - (Unicode code units) and ranges from 0 to - length()- 1. - -

        If the char value specified at the given index - is in the high-surrogate range, the following index is less - than the length of this String, and the - char value at the following index is in the - low-surrogate range, then the supplementary code point - corresponding to this surrogate pair is returned. Otherwise, - the char value at the given index is returned.

        -
        -
        Parameters:
        -
        index - the index to the char values
        -
        Returns:
        -
        the code point value of the character at the - index
        -
        Throws:
        -
        IndexOutOfBoundsException - if the index - argument is negative or not less than the length of this - string.
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        codePointBefore

        -
        public int codePointBefore(int index)
        -
        Returns the character (Unicode code point) before the specified - index. The index refers to char values - (Unicode code units) and ranges from 1 to length. - -

        If the char value at (index - 1) - is in the low-surrogate range, (index - 2) is not - negative, and the char value at (index - - 2) is in the high-surrogate range, then the - supplementary code point value of the surrogate pair is - returned. If the char value at index - - 1 is an unpaired low-surrogate or a high-surrogate, the - surrogate value is returned.

        -
        -
        Parameters:
        -
        index - the index following the code point that should be returned
        -
        Returns:
        -
        the Unicode code point value before the given index.
        -
        Throws:
        -
        IndexOutOfBoundsException - if the index - argument is less than 1 or greater than the length - of this string.
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        codePointCount

        -
        public int codePointCount(int beginIndex,
        -                          int endIndex)
        -
        Returns the number of Unicode code points in the specified text - range of this String. The text range begins at the - specified beginIndex and extends to the - char at index endIndex - 1. Thus the - length (in chars) of the text range is - endIndex-beginIndex. Unpaired surrogates within - the text range count as one code point each.
        -
        -
        Parameters:
        -
        beginIndex - the index to the first char of - the text range.
        -
        endIndex - the index after the last char of - the text range.
        -
        Returns:
        -
        the number of Unicode code points in the specified text - range
        -
        Throws:
        -
        IndexOutOfBoundsException - if the - beginIndex is negative, or endIndex - is larger than the length of this String, or - beginIndex is larger than endIndex.
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        offsetByCodePoints

        -
        public int offsetByCodePoints(int index,
        -                              int codePointOffset)
        -
        Returns the index within this String that is - offset from the given index by - codePointOffset code points. Unpaired surrogates - within the text range given by index and - codePointOffset count as one code point each.
        -
        -
        Parameters:
        -
        index - the index to be offset
        -
        codePointOffset - the offset in code points
        -
        Returns:
        -
        the index within this String
        -
        Throws:
        -
        IndexOutOfBoundsException - if index - is negative or larger then the length of this - String, or if codePointOffset is positive - and the substring starting with index has fewer - than codePointOffset code points, - or if codePointOffset is negative and the substring - before index has fewer than the absolute value - of codePointOffset code points.
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        getChars

        -
        public void getChars(int srcBegin,
        -                     int srcEnd,
        -                     char[] dst,
        -                     int dstBegin)
        -
        Copies characters from this string into the destination character - array. -

        - The first character to be copied is at index srcBegin; - the last character to be copied is at index srcEnd-1 - (thus the total number of characters to be copied is - srcEnd-srcBegin). The characters are copied into the - subarray of dst starting at index dstBegin - and ending at index: -

        -     dstbegin + (srcEnd-srcBegin) - 1
        - 
        -
        -
        Parameters:
        -
        srcBegin - index of the first character in the string - to copy.
        -
        srcEnd - index after the last character in the string - to copy.
        -
        dst - the destination array.
        -
        dstBegin - the start offset in the destination array.
        -
        Throws:
        -
        IndexOutOfBoundsException - If any of the following - is true: -
        • srcBegin is negative. -
        • srcBegin is greater than srcEnd -
        • srcEnd is greater than the length of this - string -
        • dstBegin is negative -
        • dstBegin+(srcEnd-srcBegin) is larger than - dst.length
        -
        -
      • -
      - - - -
        -
      • -

        getBytes

        -
        @Deprecated
        -public void getBytes(int srcBegin,
        -                                 int srcEnd,
        -                                 byte[] dst,
        -                                 int dstBegin)
        -
        Deprecated. This method does not properly convert characters into - bytes. As of JDK 1.1, the preferred way to do this is via the - getBytes() method, which uses the platform's default charset.
        -
        Copies characters from this string into the destination byte array. Each - byte receives the 8 low-order bits of the corresponding character. The - eight high-order bits of each character are not copied and do not - participate in the transfer in any way. - -

        The first character to be copied is at index srcBegin; the - last character to be copied is at index srcEnd-1. The total - number of characters to be copied is srcEnd-srcBegin. The - characters, converted to bytes, are copied into the subarray of dst starting at index dstBegin and ending at index: - -

        -     dstbegin + (srcEnd-srcBegin) - 1
        - 
        -
        -
        Parameters:
        -
        srcBegin - Index of the first character in the string to copy
        -
        srcEnd - Index after the last character in the string to copy
        -
        dst - The destination array
        -
        dstBegin - The start offset in the destination array
        -
        Throws:
        -
        IndexOutOfBoundsException - If any of the following is true: -
          -
        • srcBegin is negative -
        • srcBegin is greater than srcEnd -
        • srcEnd is greater than the length of this String -
        • dstBegin is negative -
        • dstBegin+(srcEnd-srcBegin) is larger than dst.length -
        -
        -
      • -
      - - - -
        -
      • -

        getBytes

        -
        public byte[] getBytes(String charsetName)
        -                throws UnsupportedEncodingException
        -
        Encodes this String into a sequence of bytes using the named - charset, storing the result into a new byte array. - -

        The behavior of this method when this string cannot be encoded in - the given charset is unspecified. The CharsetEncoder class should be used when more control - over the encoding process is required.

        -
        -
        Parameters:
        -
        charsetName - The name of a supported charset
        -
        Returns:
        -
        The resultant byte array
        -
        Throws:
        -
        UnsupportedEncodingException - If the named charset is not supported
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        getBytes

        -
        public byte[] getBytes(Charset charset)
        -
        Encodes this String into a sequence of bytes using the given - charset, storing the result into a - new byte array. - -

        This method always replaces malformed-input and unmappable-character - sequences with this charset's default replacement byte array. The - CharsetEncoder class should be used when more - control over the encoding process is required.

        -
        -
        Parameters:
        -
        charset - The Charset to be used to encode - the String
        -
        Returns:
        -
        The resultant byte array
        -
        Since:
        -
        1.6
        -
        -
      • -
      - - - -
        -
      • -

        getBytes

        -
        public byte[] getBytes()
        -
        Encodes this String into a sequence of bytes using the - platform's default charset, storing the result into a new byte array. - -

        The behavior of this method when this string cannot be encoded in - the default charset is unspecified. The CharsetEncoder class should be used when more control - over the encoding process is required.

        -
        -
        Returns:
        -
        The resultant byte array
        -
        Since:
        -
        JDK1.1
        -
        -
      • -
      - - - -
        -
      • -

        equals

        -
        public boolean equals(Object anObject)
        -
        Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this - object.
        -
        -
        Overrides:
        -
        equals in class Object
        -
        Parameters:
        -
        anObject - The object to compare this String against
        -
        Returns:
        -
        true if the given object represents a String - equivalent to this string, false otherwise
        -
        See Also:
        -
        compareTo(String), -equalsIgnoreCase(String)
        -
        -
      • -
      - - - -
        -
      • -

        contentEquals

        -
        public boolean contentEquals(StringBuffer sb)
        -
        Compares this string to the specified StringBuffer. The result - is true if and only if this String represents the same - sequence of characters as the specified StringBuffer. This method - synchronizes on the StringBuffer.
        -
        -
        Parameters:
        -
        sb - The StringBuffer to compare this String against
        -
        Returns:
        -
        true if this String represents the same - sequence of characters as the specified StringBuffer, - false otherwise
        -
        Since:
        -
        1.4
        -
        -
      • -
      - - - -
        -
      • -

        contentEquals

        -
        public boolean contentEquals(CharSequence cs)
        -
        Compares this string to the specified CharSequence. The - result is true if and only if this String represents the - same sequence of char values as the specified sequence. Note that if the - CharSequence is a StringBuffer then the method - synchronizes on it.
        -
        -
        Parameters:
        -
        cs - The sequence to compare this String against
        -
        Returns:
        -
        true if this String represents the same - sequence of char values as the specified sequence, false otherwise
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        equalsIgnoreCase

        -
        public boolean equalsIgnoreCase(String anotherString)
        -
        Compares this String to another String, ignoring case - considerations. Two strings are considered equal ignoring case if they - are of the same length and corresponding characters in the two strings - are equal ignoring case. - -

        Two characters c1 and c2 are considered the same - ignoring case if at least one of the following is true: -

        -
        -
        Parameters:
        -
        anotherString - The String to compare this String against
        -
        Returns:
        -
        true if the argument is not null and it - represents an equivalent String ignoring case; false otherwise
        -
        See Also:
        -
        equals(Object)
        -
        -
      • -
      - - - -
        -
      • -

        compareTo

        -
        public int compareTo(String anotherString)
        -
        Compares two strings lexicographically. - The comparison is based on the Unicode value of each character in - the strings. The character sequence represented by this - String object is compared lexicographically to the - character sequence represented by the argument string. The result is - a negative integer if this String object - lexicographically precedes the argument string. The result is a - positive integer if this String object lexicographically - follows the argument string. The result is zero if the strings - are equal; compareTo returns 0 exactly when - the equals(Object) method would return true. -

        - This is the definition of lexicographic ordering. If two strings are - different, then either they have different characters at some index - that is a valid index for both strings, or their lengths are different, - or both. If they have different characters at one or more index - positions, let k be the smallest such index; then the string - whose character at position k has the smaller value, as - determined by using the < operator, lexicographically precedes the - other string. In this case, compareTo returns the - difference of the two character values at position k in - the two string -- that is, the value: -

        - this.charAt(k)-anotherString.charAt(k)
        - 
        - If there is no index position at which they differ, then the shorter - string lexicographically precedes the longer string. In this case, - compareTo returns the difference of the lengths of the - strings -- that is, the value: -
        - this.length()-anotherString.length()
        - 
        -
        -
        Specified by:
        -
        compareTo in interface Comparable<String>
        -
        Parameters:
        -
        anotherString - the String to be compared.
        -
        Returns:
        -
        the value 0 if the argument string is equal to - this string; a value less than 0 if this string - is lexicographically less than the string argument; and a - value greater than 0 if this string is - lexicographically greater than the string argument.
        -
        -
      • -
      - - - -
        -
      • -

        compareToIgnoreCase

        -
        public int compareToIgnoreCase(String str)
        -
        Compares two strings lexicographically, ignoring case - differences. This method returns an integer whose sign is that of - calling compareTo with normalized versions of the strings - where case differences have been eliminated by calling - Character.toLowerCase(Character.toUpperCase(character)) on - each character. -

        - Note that this method does not take locale into account, - and will result in an unsatisfactory ordering for certain locales. - The java.text package provides collators to allow - locale-sensitive ordering.

        -
        -
        Parameters:
        -
        str - the String to be compared.
        -
        Returns:
        -
        a negative integer, zero, or a positive integer as the - specified String is greater than, equal to, or less - than this String, ignoring case considerations.
        -
        Since:
        -
        1.2
        -
        See Also:
        -
        Collator.compare(String, String)
        -
        -
      • -
      - - - -
        -
      • -

        regionMatches

        -
        public boolean regionMatches(int toffset,
        -                             String other,
        -                             int ooffset,
        -                             int len)
        -
        Tests if two string regions are equal. -

        - A substring of this String object is compared to a substring - of the argument other. The result is true if these substrings - represent identical character sequences. The substring of this - String object to be compared begins at index toffset - and has length len. The substring of other to be compared - begins at index ooffset and has length len. The - result is false if and only if at least one of the following - is true: -

        • toffset is negative. -
        • ooffset is negative. -
        • toffset+len is greater than the length of this - String object. -
        • ooffset+len is greater than the length of the other - argument. -
        • There is some nonnegative integer k less than len - such that: - this.charAt(toffset + k) != other.charAt(ooffset + - k) -
        -
        -
        Parameters:
        -
        toffset - the starting offset of the subregion in this string.
        -
        other - the string argument.
        -
        ooffset - the starting offset of the subregion in the string - argument.
        -
        len - the number of characters to compare.
        -
        Returns:
        -
        true if the specified subregion of this string - exactly matches the specified subregion of the string argument; - false otherwise.
        -
        -
      • -
      - - - -
        -
      • -

        regionMatches

        -
        public boolean regionMatches(boolean ignoreCase,
        -                             int toffset,
        -                             String other,
        -                             int ooffset,
        -                             int len)
        -
        Tests if two string regions are equal. -

        - A substring of this String object is compared to a substring - of the argument other. The result is true if these - substrings represent character sequences that are the same, ignoring - case if and only if ignoreCase is true. The substring of - this String object to be compared begins at index - toffset and has length len. The substring of - other to be compared begins at index ooffset and - has length len. The result is false if and only if - at least one of the following is true: -

        • toffset is negative. -
        • ooffset is negative. -
        • toffset+len is greater than the length of this - String object. -
        • ooffset+len is greater than the length of the other - argument. -
        • ignoreCase is false and there is some nonnegative - integer k less than len such that: -
          - this.charAt(toffset+k) != other.charAt(ooffset+k)
          - 
          -
        • ignoreCase is true and there is some nonnegative - integer k less than len such that: -
          - Character.toLowerCase(this.charAt(toffset+k)) !=
          -     Character.toLowerCase(other.charAt(ooffset+k))
          - 
          - and: -
          - Character.toUpperCase(this.charAt(toffset+k)) !=
          -         Character.toUpperCase(other.charAt(ooffset+k))
          - 
          -
        -
        -
        Parameters:
        -
        ignoreCase - if true, ignore case when comparing - characters.
        -
        toffset - the starting offset of the subregion in this - string.
        -
        other - the string argument.
        -
        ooffset - the starting offset of the subregion in the string - argument.
        -
        len - the number of characters to compare.
        -
        Returns:
        -
        true if the specified subregion of this string - matches the specified subregion of the string argument; - false otherwise. Whether the matching is exact - or case insensitive depends on the ignoreCase - argument.
        -
        -
      • -
      - - - -
        -
      • -

        startsWith

        -
        public boolean startsWith(String prefix,
        -                          int toffset)
        -
        Tests if the substring of this string beginning at the - specified index starts with the specified prefix.
        -
        -
        Parameters:
        -
        prefix - the prefix.
        -
        toffset - where to begin looking in this string.
        -
        Returns:
        -
        true if the character sequence represented by the - argument is a prefix of the substring of this object starting - at index toffset; false otherwise. - The result is false if toffset is - negative or greater than the length of this - String object; otherwise the result is the same - as the result of the expression -
        -          this.substring(toffset).startsWith(prefix)
        -          
        -
        -
      • -
      - - - -
        -
      • -

        startsWith

        -
        public boolean startsWith(String prefix)
        -
        Tests if this string starts with the specified prefix.
        -
        -
        Parameters:
        -
        prefix - the prefix.
        -
        Returns:
        -
        true if the character sequence represented by the - argument is a prefix of the character sequence represented by - this string; false otherwise. - Note also that true will be returned if the - argument is an empty string or is equal to this - String object as determined by the - equals(Object) method.
        -
        Since:
        -
        1. 0
        -
        -
      • -
      - - - -
        -
      • -

        endsWith

        -
        public boolean endsWith(String suffix)
        -
        Tests if this string ends with the specified suffix.
        -
        -
        Parameters:
        -
        suffix - the suffix.
        -
        Returns:
        -
        true if the character sequence represented by the - argument is a suffix of the character sequence represented by - this object; false otherwise. Note that the - result will be true if the argument is the - empty string or is equal to this String object - as determined by the equals(Object) method.
        -
        -
      • -
      - - - -
        -
      • -

        hashCode

        -
        public int hashCode()
        -
        Returns a hash code for this string. The hash code for a - String object is computed as -
        - s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        - 
        - using int arithmetic, where s[i] is the - ith character of the string, n is the length of - the string, and ^ indicates exponentiation. - (The hash value of the empty string is zero.)
        -
        -
        Overrides:
        -
        hashCode in class Object
        -
        Returns:
        -
        a hash code value for this object.
        -
        See Also:
        -
        Object.equals(java.lang.Object), -System.identityHashCode(java.lang.Object)
        -
        -
      • -
      - - - -
        -
      • -

        indexOf

        -
        public int indexOf(int ch)
        -
        Returns the index within this string of the first occurrence of - the specified character. If a character with value - ch occurs in the character sequence represented by - this String object, then the index (in Unicode - code units) of the first such occurrence is returned. For - values of ch in the range from 0 to 0xFFFF - (inclusive), this is the smallest value k such that: -
        - this.charAt(k) == ch
        - 
        - is true. For other values of ch, it is the - smallest value k such that: -
        - this.codePointAt(k) == ch
        - 
        - is true. In either case, if no such character occurs in this - string, then -1 is returned.
        -
        -
        Parameters:
        -
        ch - a character (Unicode code point).
        -
        Returns:
        -
        the index of the first occurrence of the character in the - character sequence represented by this object, or - -1 if the character does not occur.
        -
        -
      • -
      - - - -
        -
      • -

        indexOf

        -
        public int indexOf(int ch,
        -                   int fromIndex)
        -
        Returns the index within this string of the first occurrence of the - specified character, starting the search at the specified index. -

        - If a character with value ch occurs in the - character sequence represented by this String - object at an index no smaller than fromIndex, then - the index of the first such occurrence is returned. For values - of ch in the range from 0 to 0xFFFF (inclusive), - this is the smallest value k such that: -

        - (this.charAt(k) == ch)  && (k >= fromIndex)
        - 
        - is true. For other values of ch, it is the - smallest value k such that: -
        - (this.codePointAt(k) == ch)  && (k >= fromIndex)
        - 
        - is true. In either case, if no such character occurs in this - string at or after position fromIndex, then - -1 is returned. - -

        - There is no restriction on the value of fromIndex. If it - is negative, it has the same effect as if it were zero: this entire - string may be searched. If it is greater than the length of this - string, it has the same effect as if it were equal to the length of - this string: -1 is returned. - -

        All indices are specified in char values - (Unicode code units).

        -
        -
        Parameters:
        -
        ch - a character (Unicode code point).
        -
        fromIndex - the index to start the search from.
        -
        Returns:
        -
        the index of the first occurrence of the character in the - character sequence represented by this object that is greater - than or equal to fromIndex, or -1 - if the character does not occur.
        -
        -
      • -
      - - - -
        -
      • -

        lastIndexOf

        -
        public int lastIndexOf(int ch)
        -
        Returns the index within this string of the last occurrence of - the specified character. For values of ch in the - range from 0 to 0xFFFF (inclusive), the index (in Unicode code - units) returned is the largest value k such that: -
        - this.charAt(k) == ch
        - 
        - is true. For other values of ch, it is the - largest value k such that: -
        - this.codePointAt(k) == ch
        - 
        - is true. In either case, if no such character occurs in this - string, then -1 is returned. The - String is searched backwards starting at the last - character.
        -
        -
        Parameters:
        -
        ch - a character (Unicode code point).
        -
        Returns:
        -
        the index of the last occurrence of the character in the - character sequence represented by this object, or - -1 if the character does not occur.
        -
        -
      • -
      - - - -
        -
      • -

        lastIndexOf

        -
        public int lastIndexOf(int ch,
        -                       int fromIndex)
        -
        Returns the index within this string of the last occurrence of - the specified character, searching backward starting at the - specified index. For values of ch in the range - from 0 to 0xFFFF (inclusive), the index returned is the largest - value k such that: -
        - (this.charAt(k) == ch)  && (k <= fromIndex)
        - 
        - is true. For other values of ch, it is the - largest value k such that: -
        - (this.codePointAt(k) == ch)  && (k <= fromIndex)
        - 
        - is true. In either case, if no such character occurs in this - string at or before position fromIndex, then - -1 is returned. - -

        All indices are specified in char values - (Unicode code units).

        -
        -
        Parameters:
        -
        ch - a character (Unicode code point).
        -
        fromIndex - the index to start the search from. There is no - restriction on the value of fromIndex. If it is - greater than or equal to the length of this string, it has - the same effect as if it were equal to one less than the - length of this string: this entire string may be searched. - If it is negative, it has the same effect as if it were -1: - -1 is returned.
        -
        Returns:
        -
        the index of the last occurrence of the character in the - character sequence represented by this object that is less - than or equal to fromIndex, or -1 - if the character does not occur before that point.
        -
        -
      • -
      - - - -
        -
      • -

        indexOf

        -
        public int indexOf(String str)
        -
        Returns the index within this string of the first occurrence of the - specified substring. - -

        The returned index is the smallest value k for which: -

        - this.startsWith(str, k)
        - 
        - If no such value of k exists, then -1 is returned.
        -
        -
        Parameters:
        -
        str - the substring to search for.
        -
        Returns:
        -
        the index of the first occurrence of the specified substring, - or -1 if there is no such occurrence.
        -
        -
      • -
      - - - -
        -
      • -

        indexOf

        -
        public int indexOf(String str,
        -                   int fromIndex)
        -
        Returns the index within this string of the first occurrence of the - specified substring, starting at the specified index. - -

        The returned index is the smallest value k for which: -

        - k >= fromIndex  && this.startsWith(str, k)
        - 
        - If no such value of k exists, then -1 is returned.
        -
        -
        Parameters:
        -
        str - the substring to search for.
        -
        fromIndex - the index from which to start the search.
        -
        Returns:
        -
        the index of the first occurrence of the specified substring, - starting at the specified index, - or -1 if there is no such occurrence.
        -
        -
      • -
      - - - -
        -
      • -

        lastIndexOf

        -
        public int lastIndexOf(String str)
        -
        Returns the index within this string of the last occurrence of the - specified substring. The last occurrence of the empty string "" - is considered to occur at the index value this.length(). - -

        The returned index is the largest value k for which: -

        - this.startsWith(str, k)
        - 
        - If no such value of k exists, then -1 is returned.
        -
        -
        Parameters:
        -
        str - the substring to search for.
        -
        Returns:
        -
        the index of the last occurrence of the specified substring, - or -1 if there is no such occurrence.
        -
        -
      • -
      - - - -
        -
      • -

        lastIndexOf

        -
        public int lastIndexOf(String str,
        -                       int fromIndex)
        -
        Returns the index within this string of the last occurrence of the - specified substring, searching backward starting at the specified index. - -

        The returned index is the largest value k for which: -

        - k  <= fromIndex  && this.startsWith(str, k)
        - 
        - If no such value of k exists, then -1 is returned.
        -
        -
        Parameters:
        -
        str - the substring to search for.
        -
        fromIndex - the index to start the search from.
        -
        Returns:
        -
        the index of the last occurrence of the specified substring, - searching backward from the specified index, - or -1 if there is no such occurrence.
        -
        -
      • -
      - - - -
        -
      • -

        substring

        -
        public String substring(int beginIndex)
        -
        Returns a string that is a substring of this string. The - substring begins with the character at the specified index and - extends to the end of this string.

        - Examples: -

        - "unhappy".substring(2) returns "happy"
        - "Harbison".substring(3) returns "bison"
        - "emptiness".substring(9) returns "" (an empty string)
        - 
        -
        -
        Parameters:
        -
        beginIndex - the beginning index, inclusive.
        -
        Returns:
        -
        the specified substring.
        -
        Throws:
        -
        IndexOutOfBoundsException - if - beginIndex is negative or larger than the - length of this String object.
        -
        -
      • -
      - - - -
        -
      • -

        substring

        -
        public String substring(int beginIndex,
        -                        int endIndex)
        -
        Returns a string that is a substring of this string. The - substring begins at the specified beginIndex and - extends to the character at index endIndex - 1. - Thus the length of the substring is endIndex-beginIndex. -

        - Examples: -

        - "hamburger".substring(4, 8) returns "urge"
        - "smiles".substring(1, 5) returns "mile"
        - 
        -
        -
        Parameters:
        -
        beginIndex - the beginning index, inclusive.
        -
        endIndex - the ending index, exclusive.
        -
        Returns:
        -
        the specified substring.
        -
        Throws:
        -
        IndexOutOfBoundsException - if the - beginIndex is negative, or - endIndex is larger than the length of - this String object, or - beginIndex is larger than - endIndex.
        -
        -
      • -
      - - - -
        -
      • -

        subSequence

        -
        public CharSequence subSequence(int beginIndex,
        -                                int endIndex)
        -
        Returns a character sequence that is a subsequence of this sequence. - -

        An invocation of this method of the form - -

        - str.subSequence(begin, end)
        - - behaves in exactly the same way as the invocation - -
        - str.substring(begin, end)
        -
        -
        Specified by:
        -
        subSequence in interface CharSequence
        -
        API Note:
        -
        This method is defined so that the String class can implement - the CharSequence interface.
        -
        Parameters:
        -
        beginIndex - the begin index, inclusive.
        -
        endIndex - the end index, exclusive.
        -
        Returns:
        -
        the specified subsequence.
        -
        Throws:
        -
        IndexOutOfBoundsException - if beginIndex or endIndex is negative, - if endIndex is greater than length(), - or if beginIndex is greater than endIndex
        -
        Since:
        -
        1.4
        -
        -
      • -
      - - - -
        -
      • -

        concat

        -
        public String concat(String str)
        -
        Concatenates the specified string to the end of this string. -

        - If the length of the argument string is 0, then this - String object is returned. Otherwise, a - String object is returned that represents a character - sequence that is the concatenation of the character sequence - represented by this String object and the character - sequence represented by the argument string.

        - Examples: -

        - "cares".concat("s") returns "caress"
        - "to".concat("get").concat("her") returns "together"
        - 
        -
        -
        Parameters:
        -
        str - the String that is concatenated to the end - of this String.
        -
        Returns:
        -
        a string that represents the concatenation of this object's - characters followed by the string argument's characters.
        -
        -
      • -
      - - - -
        -
      • -

        replace

        -
        public String replace(char oldChar,
        -                      char newChar)
        -
        Returns a string resulting from replacing all occurrences of - oldChar in this string with newChar. -

        - If the character oldChar does not occur in the - character sequence represented by this String object, - then a reference to this String object is returned. - Otherwise, a String object is returned that - represents a character sequence identical to the character sequence - represented by this String object, except that every - occurrence of oldChar is replaced by an occurrence - of newChar. -

        - Examples: -

        - "mesquite in your cellar".replace('e', 'o')
        -         returns "mosquito in your collar"
        - "the war of baronets".replace('r', 'y')
        -         returns "the way of bayonets"
        - "sparring with a purple porpoise".replace('p', 't')
        -         returns "starring with a turtle tortoise"
        - "JonL".replace('q', 'x') returns "JonL" (no change)
        - 
        -
        -
        Parameters:
        -
        oldChar - the old character.
        -
        newChar - the new character.
        -
        Returns:
        -
        a string derived from this string by replacing every - occurrence of oldChar with newChar.
        -
        -
      • -
      - - - -
        -
      • -

        matches

        -
        public boolean matches(String regex)
        -
        Tells whether or not this string matches the given regular expression. - -

        An invocation of this method of the form - str.matches(regex) yields exactly the - same result as the expression - -

        - Pattern.matches(regex, str) -
        -
        -
        Parameters:
        -
        regex - the regular expression to which this string is to be matched
        -
        Returns:
        -
        true if, and only if, this string matches the - given regular expression
        -
        Throws:
        -
        PatternSyntaxException - if the regular expression's syntax is invalid
        -
        Since:
        -
        1.4
        -
        See Also:
        -
        Pattern
        -
        -
      • -
      - - - -
        -
      • -

        contains

        -
        public boolean contains(CharSequence s)
        -
        Returns true if and only if this string contains the specified - sequence of char values.
        -
        -
        Parameters:
        -
        s - the sequence to search for
        -
        Returns:
        -
        true if this string contains s, false otherwise
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        replaceFirst

        -
        public String replaceFirst(String regex,
        -                           String replacement)
        -
        Replaces the first substring of this string that matches the given regular expression with the - given replacement. - -

        An invocation of this method of the form - str.replaceFirst(regex, repl) - yields exactly the same result as the expression - -

        - - Pattern.compile(regex).matcher(str).replaceFirst(repl) - -
        - -

        - Note that backslashes (\) and dollar signs ($) in the - replacement string may cause the results to be different than if it were - being treated as a literal replacement string; see - Matcher.replaceFirst(java.lang.String). - Use Matcher.quoteReplacement(java.lang.String) to suppress the special - meaning of these characters, if desired.

        -
        -
        Parameters:
        -
        regex - the regular expression to which this string is to be matched
        -
        replacement - the string to be substituted for the first match
        -
        Returns:
        -
        The resulting String
        -
        Throws:
        -
        PatternSyntaxException - if the regular expression's syntax is invalid
        -
        Since:
        -
        1.4
        -
        See Also:
        -
        Pattern
        -
        -
      • -
      - - - -
        -
      • -

        replaceAll

        -
        public String replaceAll(String regex,
        -                         String replacement)
        -
        Replaces each substring of this string that matches the given regular expression with the - given replacement. - -

        An invocation of this method of the form - str.replaceAll(regex, repl) - yields exactly the same result as the expression - -

        - - Pattern.compile(regex).matcher(str).replaceAll(repl) - -
        - -

        - Note that backslashes (\) and dollar signs ($) in the - replacement string may cause the results to be different than if it were - being treated as a literal replacement string; see - Matcher.replaceAll. - Use Matcher.quoteReplacement(java.lang.String) to suppress the special - meaning of these characters, if desired.

        -
        -
        Parameters:
        -
        regex - the regular expression to which this string is to be matched
        -
        replacement - the string to be substituted for each match
        -
        Returns:
        -
        The resulting String
        -
        Throws:
        -
        PatternSyntaxException - if the regular expression's syntax is invalid
        -
        Since:
        -
        1.4
        -
        See Also:
        -
        Pattern
        -
        -
      • -
      - - - -
        -
      • -

        replace

        -
        public String replace(CharSequence target,
        -                      CharSequence replacement)
        -
        Replaces each substring of this string that matches the literal target - sequence with the specified literal replacement sequence. The - replacement proceeds from the beginning of the string to the end, for - example, replacing "aa" with "b" in the string "aaa" will result in - "ba" rather than "ab".
        -
        -
        Parameters:
        -
        target - The sequence of char values to be replaced
        -
        replacement - The replacement sequence of char values
        -
        Returns:
        -
        The resulting string
        -
        Since:
        -
        1.5
        -
        -
      • -
      - - - -
        -
      • -

        split

        -
        public String[] split(String regex,
        -                      int limit)
        -
        Splits this string around matches of the given - regular expression. - -

        The array returned by this method contains each substring of this - string that is terminated by another substring that matches the given - expression or is terminated by the end of the string. The substrings in - the array are in the order in which they occur in this string. If the - expression does not match any part of the input then the resulting array - has just one element, namely this string. - -

        When there is a positive-width match at the beginning of this - string then an empty leading substring is included at the beginning - of the resulting array. A zero-width match at the beginning however - never produces such empty leading substring. - -

        The limit parameter controls the number of times the - pattern is applied and therefore affects the length of the resulting - array. If the limit n is greater than zero then the pattern - will be applied at most n - 1 times, the array's - length will be no greater than n, and the array's last entry - will contain all input beyond the last matched delimiter. If n - is non-positive then the pattern will be applied as many times as - possible and the array can have any length. If n is zero then - the pattern will be applied as many times as possible, the array can - have any length, and trailing empty strings will be discarded. - -

        The string "boo:and:foo", for example, yields the - following results with these parameters: - -

        - - - - - - - - - - - - - - - - - - - - - - - -
        RegexLimitResult
        :2{ "boo", "and:foo" }
        :5{ "boo", "and", "foo" }
        :-2{ "boo", "and", "foo" }
        o5{ "b", "", ":and:f", "", "" }
        o-2{ "b", "", ":and:f", "", "" }
        o0{ "b", "", ":and:f" }
        - -

        An invocation of this method of the form - str.split(regex, n) - yields the same result as the expression - -

        - - Pattern.compile(regex).split(strn) - -
        -
        -
        Parameters:
        -
        regex - the delimiting regular expression
        -
        limit - the result threshold, as described above
        -
        Returns:
        -
        the array of strings computed by splitting this string - around matches of the given regular expression
        -
        Throws:
        -
        PatternSyntaxException - if the regular expression's syntax is invalid
        -
        Since:
        -
        1.4
        -
        See Also:
        -
        Pattern
        -
        -
      • -
      - - - -
        -
      • -

        split

        -
        public String[] split(String regex)
        -
        Splits this string around matches of the given regular expression. - -

        This method works as if by invoking the two-argument split method with the given expression and a limit - argument of zero. Trailing empty strings are therefore not included in - the resulting array. - -

        The string "boo:and:foo", for example, yields the following - results with these expressions: - -

        - - - - - - - - -
        RegexResult
        :{ "boo", "and", "foo" }
        o{ "b", "", ":and:f" }
        -
        -
        Parameters:
        -
        regex - the delimiting regular expression
        -
        Returns:
        -
        the array of strings computed by splitting this string - around matches of the given regular expression
        -
        Throws:
        -
        PatternSyntaxException - if the regular expression's syntax is invalid
        -
        Since:
        -
        1.4
        -
        See Also:
        -
        Pattern
        -
        -
      • -
      - - - -
        -
      • -

        join

        -
        public static String join(CharSequence delimiter,
        -                          CharSequence... elements)
        -
        Returns a new String composed of copies of the - CharSequence elements joined together with a copy of - the specified delimiter. - -
        For example, -
        
        -     String message = String.join("-", "Java", "is", "cool");
        -     // message returned is: "Java-is-cool"
        - 
        - - Note that if an element is null, then "null" is added.
        -
        -
        Parameters:
        -
        delimiter - the delimiter that separates each element
        -
        elements - the elements to join together.
        -
        Returns:
        -
        a new String that is composed of the elements - separated by the delimiter
        -
        Throws:
        -
        NullPointerException - If delimiter or elements - is null
        -
        Since:
        -
        1.8
        -
        See Also:
        -
        StringJoiner
        -
        -
      • -
      - - - -
        -
      • -

        join

        -
        public static String join(CharSequence delimiter,
        -                          Iterable<? extends CharSequence> elements)
        -
        Returns a new String composed of copies of the - CharSequence elements joined together with a copy of the - specified delimiter. - -
        For example, -
        
        -     List<String> strings = new LinkedList<>();
        -     strings.add("Java");strings.add("is");
        -     strings.add("cool");
        -     String message = String.join(" ", strings);
        -     //message returned is: "Java is cool"
        -
        -     Set<String> strings = new LinkedHashSet<>();
        -     strings.add("Java"); strings.add("is");
        -     strings.add("very"); strings.add("cool");
        -     String message = String.join("-", strings);
        -     //message returned is: "Java-is-very-cool"
        - 
        - - Note that if an individual element is null, then "null" is added.
        -
        -
        Parameters:
        -
        delimiter - a sequence of characters that is used to separate each - of the elements in the resulting String
        -
        elements - an Iterable that will have its elements - joined together.
        -
        Returns:
        -
        a new String that is composed from the elements - argument
        -
        Throws:
        -
        NullPointerException - If delimiter or elements - is null
        -
        Since:
        -
        1.8
        -
        See Also:
        -
        join(CharSequence,CharSequence...), -StringJoiner
        -
        -
      • -
      - - - -
        -
      • -

        toLowerCase

        -
        public String toLowerCase(Locale locale)
        -
        Converts all of the characters in this String to lower - case using the rules of the given Locale. Case mapping is based - on the Unicode Standard version specified by the Character - class. Since case mappings are not always 1:1 char mappings, the resulting - String may be a different length than the original String. -

        - Examples of lowercase mappings are in the following table: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Language Code of LocaleUpper CaseLower CaseDescription
        tr (Turkish)\u0130\u0069capital letter I with dot above -> small letter i
        tr (Turkish)\u0049\u0131capital letter I -> small letter dotless i
        (all)French Friesfrench frieslowercased all chars in String
        (all)capiotacapchi - capthetacapupsil - capsigmaiotachi - thetaupsilon - sigmalowercased all chars in String

        -
        -
        Parameters:
        -
        locale - use the case transformation rules for this locale
        -
        Returns:
        -
        the String, converted to lowercase.
        -
        Since:
        -
        1.1
        -
        See Also:
        -
        toLowerCase(), -toUpperCase(), -toUpperCase(Locale)
        -
        -
      • -
      - - - -
        -
      • -

        toLowerCase

        -
        public String toLowerCase()
        -
        Converts all of the characters in this String to lower - case using the rules of the default locale. This is equivalent to calling - toLowerCase(Locale.getDefault()). -

        - Note: This method is locale sensitive, and may produce unexpected - results if used for strings that are intended to be interpreted locale - independently. - Examples are programming language identifiers, protocol keys, and HTML - tags. - For instance, "TITLE".toLowerCase() in a Turkish locale - returns "t\u0131tle", where '\u0131' is the - LATIN SMALL LETTER DOTLESS I character. - To obtain correct results for locale insensitive strings, use - toLowerCase(Locale.ROOT). -

        -
        -
        Returns:
        -
        the String, converted to lowercase.
        -
        See Also:
        -
        toLowerCase(Locale)
        -
        -
      • -
      - - - -
        -
      • -

        toUpperCase

        -
        public String toUpperCase(Locale locale)
        -
        Converts all of the characters in this String to upper - case using the rules of the given Locale. Case mapping is based - on the Unicode Standard version specified by the Character - class. Since case mappings are not always 1:1 char mappings, the resulting - String may be a different length than the original String. -

        - Examples of locale-sensitive and 1:M case mappings are in the following table. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Language Code of LocaleLower CaseUpper CaseDescription
        tr (Turkish)\u0069\u0130small letter i -> capital letter I with dot above
        tr (Turkish)\u0131\u0049small letter dotless i -> capital letter I
        (all)\u00df\u0053 \u0053small letter sharp s -> two letters: SS
        (all)FahrvergnügenFAHRVERGNÜGEN

        -
        -
        Parameters:
        -
        locale - use the case transformation rules for this locale
        -
        Returns:
        -
        the String, converted to uppercase.
        -
        Since:
        -
        1.1
        -
        See Also:
        -
        toUpperCase(), -toLowerCase(), -toLowerCase(Locale)
        -
        -
      • -
      - - - -
        -
      • -

        toUpperCase

        -
        public String toUpperCase()
        -
        Converts all of the characters in this String to upper - case using the rules of the default locale. This method is equivalent to - toUpperCase(Locale.getDefault()). -

        - Note: This method is locale sensitive, and may produce unexpected - results if used for strings that are intended to be interpreted locale - independently. - Examples are programming language identifiers, protocol keys, and HTML - tags. - For instance, "title".toUpperCase() in a Turkish locale - returns "T\u0130TLE", where '\u0130' is the - LATIN CAPITAL LETTER I WITH DOT ABOVE character. - To obtain correct results for locale insensitive strings, use - toUpperCase(Locale.ROOT). -

        -
        -
        Returns:
        -
        the String, converted to uppercase.
        -
        See Also:
        -
        toUpperCase(Locale)
        -
        -
      • -
      - - - -
        -
      • -

        trim

        -
        public String trim()
        -
        Returns a string whose value is this string, with any leading and trailing - whitespace removed. -

        - If this String object represents an empty character - sequence, or the first and last characters of character sequence - represented by this String object both have codes - greater than '\u0020' (the space character), then a - reference to this String object is returned. -

        - Otherwise, if there is no character with a code greater than - '\u0020' in the string, then a - String object representing an empty string is - returned. -

        - Otherwise, let k be the index of the first character in the - string whose code is greater than '\u0020', and let - m be the index of the last character in the string whose code - is greater than '\u0020'. A String - object is returned, representing the substring of this string that - begins with the character at index k and ends with the - character at index m-that is, the result of - this.substring(k, m + 1). -

        - This method may be used to trim whitespace (as defined above) from - the beginning and end of a string.

        -
        -
        Returns:
        -
        A string whose value is this string, with any leading and trailing white - space removed, or this string if it has no leading or - trailing white space.
        -
        -
      • -
      - - - -
        -
      • -

        toString

        -
        public String toString()
        -
        This object (which is already a string!) is itself returned.
        -
        -
        Specified by:
        -
        toString in interface CharSequence
        -
        Overrides:
        -
        toString in class Object
        -
        Returns:
        -
        the string itself.
        -
        -
      • -
      - - - -
        -
      • -

        toCharArray

        -
        public char[] toCharArray()
        -
        Converts this string to a new character array.
        -
        -
        Returns:
        -
        a newly allocated character array whose length is the length - of this string and whose contents are initialized to contain - the character sequence represented by this string.
        -
        -
      • -
      - - - -
        -
      • -

        format

        -
        public static String format(String format,
        -                            Object... args)
        -
        Returns a formatted string using the specified format string and - arguments. - -

        The locale always used is the one returned by Locale.getDefault().

        -
        -
        Parameters:
        -
        format - A format string
        -
        args - Arguments referenced by the format specifiers in the format - string. If there are more arguments than format specifiers, the - extra arguments are ignored. The number of arguments is - variable and may be zero. The maximum number of arguments is - limited by the maximum dimension of a Java array as defined by - The Java™ Virtual Machine Specification. - The behaviour on a - null argument depends on the conversion.
        -
        Returns:
        -
        A formatted string
        -
        Throws:
        -
        IllegalFormatException - If a format string contains an illegal syntax, a format - specifier that is incompatible with the given arguments, - insufficient arguments given the format string, or other - illegal conditions. For specification of all possible - formatting errors, see the Details section of the - formatter class specification.
        -
        Since:
        -
        1.5
        -
        See Also:
        -
        Formatter
        -
        -
      • -
      - - - -
        -
      • -

        format

        -
        public static String format(Locale l,
        -                            String format,
        -                            Object... args)
        -
        Returns a formatted string using the specified locale, format string, - and arguments.
        -
        -
        Parameters:
        -
        l - The locale to apply during - formatting. If l is null then no localization - is applied.
        -
        format - A format string
        -
        args - Arguments referenced by the format specifiers in the format - string. If there are more arguments than format specifiers, the - extra arguments are ignored. The number of arguments is - variable and may be zero. The maximum number of arguments is - limited by the maximum dimension of a Java array as defined by - The Java™ Virtual Machine Specification. - The behaviour on a - null argument depends on the - conversion.
        -
        Returns:
        -
        A formatted string
        -
        Throws:
        -
        IllegalFormatException - If a format string contains an illegal syntax, a format - specifier that is incompatible with the given arguments, - insufficient arguments given the format string, or other - illegal conditions. For specification of all possible - formatting errors, see the Details section of the - formatter class specification
        -
        Since:
        -
        1.5
        -
        See Also:
        -
        Formatter
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(Object obj)
        -
        Returns the string representation of the Object argument.
        -
        -
        Parameters:
        -
        obj - an Object.
        -
        Returns:
        -
        if the argument is null, then a string equal to - "null"; otherwise, the value of - obj.toString() is returned.
        -
        See Also:
        -
        Object.toString()
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(char[] data)
        -
        Returns the string representation of the char array - argument. The contents of the character array are copied; subsequent - modification of the character array does not affect the returned - string.
        -
        -
        Parameters:
        -
        data - the character array.
        -
        Returns:
        -
        a String that contains the characters of the - character array.
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(char[] data,
        -                             int offset,
        -                             int count)
        -
        Returns the string representation of a specific subarray of the - char array argument. -

        - The offset argument is the index of the first - character of the subarray. The count argument - specifies the length of the subarray. The contents of the subarray - are copied; subsequent modification of the character array does not - affect the returned string.

        -
        -
        Parameters:
        -
        data - the character array.
        -
        offset - initial offset of the subarray.
        -
        count - length of the subarray.
        -
        Returns:
        -
        a String that contains the characters of the - specified subarray of the character array.
        -
        Throws:
        -
        IndexOutOfBoundsException - if offset is - negative, or count is negative, or - offset+count is larger than - data.length.
        -
        -
      • -
      - - - -
        -
      • -

        copyValueOf

        -
        public static String copyValueOf(char[] data,
        -                                 int offset,
        -                                 int count)
        - -
        -
        Parameters:
        -
        data - the character array.
        -
        offset - initial offset of the subarray.
        -
        count - length of the subarray.
        -
        Returns:
        -
        a String that contains the characters of the - specified subarray of the character array.
        -
        Throws:
        -
        IndexOutOfBoundsException - if offset is - negative, or count is negative, or - offset+count is larger than - data.length.
        -
        -
      • -
      - - - -
        -
      • -

        copyValueOf

        -
        public static String copyValueOf(char[] data)
        -
        Equivalent to valueOf(char[]).
        -
        -
        Parameters:
        -
        data - the character array.
        -
        Returns:
        -
        a String that contains the characters of the - character array.
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(boolean b)
        -
        Returns the string representation of the boolean argument.
        -
        -
        Parameters:
        -
        b - a boolean.
        -
        Returns:
        -
        if the argument is true, a string equal to - "true" is returned; otherwise, a string equal to - "false" is returned.
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(char c)
        -
        Returns the string representation of the char - argument.
        -
        -
        Parameters:
        -
        c - a char.
        -
        Returns:
        -
        a string of length 1 containing - as its single character the argument c.
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(int i)
        -
        Returns the string representation of the int argument. -

        - The representation is exactly the one returned by the - Integer.toString method of one argument.

        -
        -
        Parameters:
        -
        i - an int.
        -
        Returns:
        -
        a string representation of the int argument.
        -
        See Also:
        -
        Integer.toString(int, int)
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(long l)
        -
        Returns the string representation of the long argument. -

        - The representation is exactly the one returned by the - Long.toString method of one argument.

        -
        -
        Parameters:
        -
        l - a long.
        -
        Returns:
        -
        a string representation of the long argument.
        -
        See Also:
        -
        Long.toString(long)
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(float f)
        -
        Returns the string representation of the float argument. -

        - The representation is exactly the one returned by the - Float.toString method of one argument.

        -
        -
        Parameters:
        -
        f - a float.
        -
        Returns:
        -
        a string representation of the float argument.
        -
        See Also:
        -
        Float.toString(float)
        -
        -
      • -
      - - - -
        -
      • -

        valueOf

        -
        public static String valueOf(double d)
        -
        Returns the string representation of the double argument. -

        - The representation is exactly the one returned by the - Double.toString method of one argument.

        -
        -
        Parameters:
        -
        d - a double.
        -
        Returns:
        -
        a string representation of the double argument.
        -
        See Also:
        -
        Double.toString(double)
        -
        -
      • -
      - - - -
        -
      • -

        intern

        -
        public String intern()
        -
        Returns a canonical representation for the string object. -

        - A pool of strings, initially empty, is maintained privately by the - class String. -

        - When the intern method is invoked, if the pool already contains a - string equal to this String object as determined by - the equals(Object) method, then the string from the pool is - returned. Otherwise, this String object is added to the - pool and a reference to this String object is returned. -

        - It follows that for any two strings s and t, - s.intern() == t.intern() is true - if and only if s.equals(t) is true. -

        - All literal strings and string-valued constant expressions are - interned. String literals are defined in section 3.10.5 of the - The Java™ Language Specification.

        -
        -
        Returns:
        -
        a string that has the same contents as this string, but is - guaranteed to be from a pool of unique strings.
        -
        -
      • -
      -
    • -
    -
  • -
-
-
- - -
- - - - - - - -
Java™ Platform
Standard Ed. 8
-
- - -

Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2014, Oracle and/or its affiliates. All rights reserved.

- - diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/StringBufferInputStream.html b/src/test/resources/com/gmail/inverseconduit/javadoc/StringBufferInputStream.html deleted file mode 100644 index 626ffa3..0000000 --- a/src/test/resources/com/gmail/inverseconduit/javadoc/StringBufferInputStream.html +++ /dev/null @@ -1,557 +0,0 @@ - - - - - -StringBufferInputStream (Java Platform SE 8 ) - - - - - - - - - - - - - - - - -
- - - - - - - -
Java™ Platform
Standard Ed. 8
-
- - - -
-
compact1, compact2, compact3
-
java.io
-

Class StringBufferInputStream

-
-
- -
-
    -
  • -
    -
    All Implemented Interfaces:
    -
    Closeable, AutoCloseable
    -
    -
    -
    Deprecated.  -
    This class does not properly convert characters into bytes. As - of JDK 1.1, the preferred way to create a stream from a - string is via the StringReader class.
    -
    -
    -
    @Deprecated
    -public class StringBufferInputStream
    -extends InputStream
    -
    This class allows an application to create an input stream in - which the bytes read are supplied by the contents of a string. - Applications can also read bytes from a byte array by using a - ByteArrayInputStream. -

    - Only the low eight bits of each character in the string are used by - this class.

    -
    -
    Since:
    -
    JDK1.0
    -
    See Also:
    -
    ByteArrayInputStream, -StringReader
    -
    -
  • -
-
-
-
    -
  • - -
      -
    • - - -

      Field Summary

      - - - - - - - - - - - - - - - - - - -
      Fields 
      Modifier and TypeField and Description
      protected Stringbuffer -
      Deprecated. 
      -
      The string from which bytes are read.
      -
      protected intcount -
      Deprecated. 
      -
      The number of valid characters in the input stream buffer.
      -
      protected intpos -
      Deprecated. 
      -
      The index of the next character to read from the input stream buffer.
      -
      -
    • -
    - -
      -
    • - - -

      Constructor Summary

      - - - - - - - - -
      Constructors 
      Constructor and Description
      StringBufferInputStream(String s) -
      Deprecated. 
      -
      Creates a string input stream to read data from the specified string.
      -
      -
    • -
    - - -
  • -
-
-
-
    -
  • - -
      -
    • - - -

      Field Detail

      - - - -
        -
      • -

        buffer

        -
        protected String buffer
        -
        Deprecated. 
        -
        The string from which bytes are read.
        -
      • -
      - - - -
        -
      • -

        pos

        -
        protected int pos
        -
        Deprecated. 
        -
        The index of the next character to read from the input stream buffer.
        -
        -
        See Also:
        -
        buffer
        -
        -
      • -
      - - - -
        -
      • -

        count

        -
        protected int count
        -
        Deprecated. 
        -
        The number of valid characters in the input stream buffer.
        -
        -
        See Also:
        -
        buffer
        -
        -
      • -
      -
    • -
    - -
      -
    • - - -

      Constructor Detail

      - - - -
        -
      • -

        StringBufferInputStream

        -
        public StringBufferInputStream(String s)
        -
        Deprecated. 
        -
        Creates a string input stream to read data from the specified string.
        -
        -
        Parameters:
        -
        s - the underlying input buffer.
        -
        -
      • -
      -
    • -
    - -
      -
    • - - -

      Method Detail

      - - - -
        -
      • -

        read

        -
        public int read()
        -
        Deprecated. 
        -
        Reads the next byte of data from this input stream. The value - byte is returned as an int in the range - 0 to 255. If no byte is available - because the end of the stream has been reached, the value - -1 is returned. -

        - The read method of - StringBufferInputStream cannot block. It returns the - low eight bits of the next character in this input stream's buffer.

        -
        -
        Specified by:
        -
        read in class InputStream
        -
        Returns:
        -
        the next byte of data, or -1 if the end of the - stream is reached.
        -
        -
      • -
      - - - -
        -
      • -

        read

        -
        public int read(byte[] b,
        -                int off,
        -                int len)
        -
        Deprecated. 
        -
        Reads up to len bytes of data from this input stream - into an array of bytes. -

        - The read method of - StringBufferInputStream cannot block. It copies the - low eight bits from the characters in this input stream's buffer into - the byte array argument.

        -
        -
        Overrides:
        -
        read in class InputStream
        -
        Parameters:
        -
        b - the buffer into which the data is read.
        -
        off - the start offset of the data.
        -
        len - the maximum number of bytes read.
        -
        Returns:
        -
        the total number of bytes read into the buffer, or - -1 if there is no more data because the end of - the stream has been reached.
        -
        See Also:
        -
        InputStream.read()
        -
        -
      • -
      - - - -
        -
      • -

        skip

        -
        public long skip(long n)
        -
        Deprecated. 
        -
        Skips n bytes of input from this input stream. Fewer - bytes might be skipped if the end of the input stream is reached.
        -
        -
        Overrides:
        -
        skip in class InputStream
        -
        Parameters:
        -
        n - the number of bytes to be skipped.
        -
        Returns:
        -
        the actual number of bytes skipped.
        -
        -
      • -
      - - - -
        -
      • -

        available

        -
        public int available()
        -
        Deprecated. 
        -
        Returns the number of bytes that can be read from the input - stream without blocking.
        -
        -
        Overrides:
        -
        available in class InputStream
        -
        Returns:
        -
        the value of count - pos, which is the - number of bytes remaining to be read from the input buffer.
        -
        -
      • -
      - - - -
        -
      • -

        reset

        -
        public void reset()
        -
        Deprecated. 
        -
        Resets the input stream to begin reading from the first character - of this input stream's underlying buffer.
        -
        -
        Overrides:
        -
        reset in class InputStream
        -
        See Also:
        -
        InputStream.mark(int), -IOException
        -
        -
      • -
      -
    • -
    -
  • -
-
-
- - -
- - - - - - - -
Java™ Platform
Standard Ed. 8
-
- - -

Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2014, Oracle and/or its affiliates. All rights reserved.

- - diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/SuppressWarnings.html b/src/test/resources/com/gmail/inverseconduit/javadoc/SuppressWarnings.html deleted file mode 100644 index f301709..0000000 --- a/src/test/resources/com/gmail/inverseconduit/javadoc/SuppressWarnings.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - -SuppressWarnings (Java Platform SE 8 ) - - - - - - - - - -
- - - - - - - -
Java™ Platform
Standard Ed. 8
-
- - - -
-
java.lang
-

Annotation Type SuppressWarnings

-
-
-
-
    -
  • -
    -
    -
    @Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
    - @Retention(value=SOURCE)
    -public @interface SuppressWarnings
    -
    Indicates that the named compiler warnings should be suppressed in the - annotated element (and in all program elements contained in the annotated - element). Note that the set of warnings suppressed in a given element is - a superset of the warnings suppressed in all containing elements. For - example, if you annotate a class to suppress one warning and annotate a - method to suppress another, both warnings will be suppressed in the method. - -

    As a matter of style, programmers should always use this annotation - on the most deeply nested element where it is effective. If you want to - suppress a warning in a particular method, you should annotate that - method rather than its class.

    -
    -
    Since:
    -
    1.5
    -
    See The Java™ Language Specification:
    -
    4.8 Raw Types, 4.12.2 Variables of Reference Type, 5.1.9 Unchecked Conversion, 5.5.2 Checked Casts and Unchecked Casts, 9.6.3.5 @SuppressWarnings
    -
    -
  • -
-
-
-
    -
  • - -
      -
    • - - -

      Required Element Summary

      - - - - - - - - - - -
      Required Elements 
      Modifier and TypeRequired Element and Description
      String[]value -
      The set of warnings that are to be suppressed by the compiler in the - annotated element.
      -
      -
    • -
    -
  • -
-
-
-
    -
  • - -
      -
    • - - -

      Element Detail

      - - - -
        -
      • -

        value

        -
        public abstract String[] value
        -
        The set of warnings that are to be suppressed by the compiler in the - annotated element. Duplicate names are permitted. The second and - successive occurrences of a name are ignored. The presence of - unrecognized warning names is not an error: Compilers must - ignore any warning names they do not recognize. They are, however, - free to emit a warning if an annotation contains an unrecognized - warning name. - -

        The string "unchecked" is used to suppress - unchecked warnings. Compiler vendors should document the - additional warning names they support in conjunction with this - annotation type. They are encouraged to cooperate to ensure - that the same names work across multiple compilers.

        -
        -
        Returns:
        -
        the set of warnings to be suppressed
        -
        -
      • -
      -
    • -
    -
  • -
-
-
- - -
- - - - - - - -
Java™ Platform
Standard Ed. 8
-
- - -

Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2014, Oracle and/or its affiliates. All rights reserved.

- - diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/java8-allclasses-frame.html b/src/test/resources/com/gmail/inverseconduit/javadoc/java8-allclasses-frame.html deleted file mode 100644 index 99069d1..0000000 --- a/src/test/resources/com/gmail/inverseconduit/javadoc/java8-allclasses-frame.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - -All Classes (Java Platform SE 8 ) - - - - - -

All Classes

- - - diff --git a/src/test/resources/com/gmail/inverseconduit/javadoc/javadoc-file.zip b/src/test/resources/com/gmail/inverseconduit/javadoc/javadoc-file.zip deleted file mode 100644 index 1d16ba564169083d1374b012d27e7db270d6dd1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38541 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 - - - - - - -All Classes (jsoup 1.8.1 API) - - - - - - - - - - - -All Classes -
- - - - - -
Attribute -
-Attributes -
-Comment -
-Connection -
-Connection.Base -
- - - From 9bc965298f7a6137b7ca733b9ee71d7b6c14ebb7 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 09:29:43 -0500 Subject: [PATCH 02/13] Made the list of modifiers and interfaces a set. --- .../inverseconduit/javadoc/ClassInfo.java | 55 ++++++++++++++++--- .../javadoc/JavaDocAccessor.java | 46 +++++++++------- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java index a4e05e2..90c6369 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java @@ -2,9 +2,10 @@ import java.util.Collection; import java.util.List; +import java.util.Set; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; /** @@ -14,8 +15,8 @@ public class ClassInfo { private final ClassName name, superClass; private final String description; - private final List modifiers; - private final List interfaces; + private final Set modifiers; + private final Set interfaces; private final Multimap methods; private final boolean deprecated; private final LibraryZipFile zipFile; @@ -31,14 +32,26 @@ private ClassInfo(Builder builder) { zipFile = builder.zipFile; } + /** + * Gets the name of the class. + * @return the class name + */ public ClassName getName() { return name; } + /** + * Gets the name of the class's parent class. + * @return the parent class name or null if it doesn't have one + */ public ClassName getSuperClass() { return superClass; } + /** + * Gets the class description. + * @return the class description (in SO-Chat markdown) + */ public String getDescription() { return description; } @@ -59,35 +72,63 @@ public String getUrl() { return zipFile.getUrl(this); } - public List getModifiers() { + /** + * Gets the class modifiers. + * @return the class modifiers (e.g. "public, final") + */ + public Collection getModifiers() { return modifiers; } - public List getInterfaces() { + /** + * Gets the names of the interfaces that the class implements. + * @return the class's interfaces + */ + public Collection getInterfaces() { return interfaces; } + /** + * Gets info on all of the methods that have the given name. + * @param name the method name (case insensitive) + * @return the methods + */ public Collection getMethod(String name) { return methods.get(name.toLowerCase()); } + /** + * Gets the class's methods. + * @return the class's methods + */ public Collection getMethods() { return methods.values(); } + /** + * Determines whether the class is deprecated or not. + * @return true if it's deprecated, false if not + */ public boolean isDeprecated() { return deprecated; } + /** + * Gets the ZIP file that the class's parsed Javadoc info is stored in. + * @return the ZIP file + */ public LibraryZipFile getZipFile() { return zipFile; } + /** + * Builds new instances of {@link ClassInfo}. + */ public static class Builder { private ClassName name, superClass; private String description; - private ImmutableList.Builder modifiers = ImmutableList.builder(); - private ImmutableList.Builder interfaces = ImmutableList.builder(); + private ImmutableSet.Builder modifiers = ImmutableSet.builder(); + private ImmutableSet.Builder interfaces = ImmutableSet.builder(); private ImmutableMultimap.Builder methods = ImmutableMultimap.builder(); private boolean deprecated = false; private LibraryZipFile zipFile; diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 8a89adf..b1bec60 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -19,6 +19,7 @@ import com.gmail.inverseconduit.datatype.ChatMessage; import com.gmail.inverseconduit.utils.ChatBuilder; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; @@ -34,28 +35,23 @@ public class JavaDocAccessor { /** * The class modifiers to print in italics when outputting a class to the - * chat. + * chat (order matters). */ - private final Set classModifiersItalic; + private final List classModifiers; { - ImmutableSet.Builder b = new ImmutableSet.Builder<>(); - b.add("abstract"); - b.add("final"); - classModifiersItalic = b.build(); + ImmutableList.Builder b = new ImmutableList.Builder<>(); + b.add("abstract", "final"); + classModifiers = b.build(); } /** - * The class modifiers to print when outputting a class to the chat. + * The list of possible "class types". */ - private final Set classModifiers; + private final Set classTypes; { ImmutableSet.Builder b = new ImmutableSet.Builder<>(); - b.add("annotation"); - b.add("class"); - b.add("enum"); - b.add("exception"); - b.add("interface"); - classModifiers = b.build(); + b.add("annotation", "class", "enum", "exception", "interface"); + classTypes = b.build(); } /** @@ -389,17 +385,29 @@ private String printClass(ClassInfo info, int paragraph) { //print modifiers boolean deprecated = info.isDeprecated(); - for (String modifier : info.getModifiers()) { - boolean italic = classModifiersItalic.contains(modifier); - if (!italic && !classModifiers.contains(modifier)) { + Collection infoModifiers = info.getModifiers(); + + //add class modifiers (order matters) + for (String classModifier : classModifiers) { + if (!infoModifiers.contains(classModifier)) { continue; } - if (italic) cb.italic(); + cb.italic(); + if (deprecated) cb.strike(); + cb.tag(classModifier); + if (deprecated) cb.strike(); + cb.italic(); + cb.append(' '); + } + + Collection classType = new HashSet<>(infoModifiers); + classType.retainAll(classTypes); + //there should be only one remaining element in the collection, but use a foreach loop just incase + for (String modifier : classType) { if (deprecated) cb.strike(); cb.tag(modifier); if (deprecated) cb.strike(); - if (italic) cb.italic(); cb.append(' '); } From 63ebe5091ec93b619dc6949aff52b7c45e211970 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 10:31:47 -0500 Subject: [PATCH 03/13] Stored method modifiers in a set. --- .../javadoc/JavaDocAccessor.java | 35 ++++++++++--------- .../inverseconduit/javadoc/MethodInfo.java | 11 +++--- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index b1bec60..10a57e0 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -24,7 +24,9 @@ import com.google.common.collect.Multimap; /** - * Handles "javadoc" commands. + * Generates chat message responses to the "javadoc" command. Also generates + * responses to numeric chat messages that users enter when the javadoc command + * presents them with a list of choices. */ public class JavaDocAccessor { private final JavadocDao dao; @@ -34,8 +36,9 @@ public class JavaDocAccessor { private final long choiceTimeout = TimeUnit.SECONDS.toMillis(30); /** - * The class modifiers to print in italics when outputting a class to the - * chat (order matters). + * "Flags" that a class can have. They are defined in a List because, if a + * class has multiple modifiers, I want them to be displayed in a consistent + * order. */ private final List classModifiers; { @@ -45,7 +48,9 @@ public class JavaDocAccessor { } /** - * The list of possible "class types". + * The list of possible "class types". Each class *should* have exactly one + * type, but there's no explicit check for this (things shouldn't break if a + * class does not have exactly one). */ private final Set classTypes; { @@ -243,11 +248,9 @@ private String printMethod(MethodInfo methodInfo, ClassInfo classInfo, int parag //print modifiers boolean deprecated = methodInfo.isDeprecated(); - for (String modifier : methodInfo.getModifiers()) { - if (methodModifiersToIgnore.contains(modifier)) { - continue; - } - + Collection modifiersToPrint = new ArrayList(methodInfo.getModifiers()); + modifiersToPrint.removeAll(methodModifiersToIgnore); + for (String modifier : modifiersToPrint) { if (deprecated) cb.strike(); cb.tag(modifier); if (deprecated) cb.strike(); @@ -386,13 +389,11 @@ private String printClass(ClassInfo info, int paragraph) { //print modifiers boolean deprecated = info.isDeprecated(); Collection infoModifiers = info.getModifiers(); + List modifiersToPrint = new ArrayList<>(classModifiers); + modifiersToPrint.retainAll(infoModifiers); - //add class modifiers (order matters) - for (String classModifier : classModifiers) { - if (!infoModifiers.contains(classModifier)) { - continue; - } - + //add class modifiers + for (String classModifier : modifiersToPrint) { cb.italic(); if (deprecated) cb.strike(); cb.tag(classModifier); @@ -401,8 +402,8 @@ private String printClass(ClassInfo info, int paragraph) { cb.append(' '); } - Collection classType = new HashSet<>(infoModifiers); - classType.retainAll(classTypes); + Collection classType = new HashSet<>(classTypes); + classType.retainAll(infoModifiers); //there should be only one remaining element in the collection, but use a foreach loop just incase for (String modifier : classType) { if (deprecated) cb.strike(); diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java index 26c67a0..09c0b65 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java @@ -1,9 +1,12 @@ package com.gmail.inverseconduit.javadoc; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * Contains information on a method. @@ -11,7 +14,7 @@ */ public class MethodInfo { private final String name, description, urlAnchor; - private final List modifiers; + private final Set modifiers; private final List parameters; private final ClassName returnValue; private final boolean deprecated; @@ -70,7 +73,7 @@ public String getDescription() { * Gets the method modifiers. * @return the method modifiers (e.g. "public", "static") */ - public List getModifiers() { + public Collection getModifiers() { return modifiers; } @@ -138,7 +141,7 @@ public String getSignatureString() { public static class Builder { private String name; - private ImmutableList.Builder modifiers = ImmutableList.builder(); + private ImmutableSet.Builder modifiers = ImmutableSet.builder(); private ImmutableList.Builder parameters = ImmutableList.builder(); private String description; private ClassName returnValue; @@ -149,7 +152,7 @@ public Builder name(String name) { return this; } - public Builder modifiers(List modifiers) { + public Builder modifiers(Collection modifiers) { this.modifiers.addAll(modifiers); return this; } From 8c040153fd9833ba478b2a92a69173efc9f23f8e Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 10:34:53 -0500 Subject: [PATCH 04/13] Renamed "ClassName.getFull()" to "getFullyQualified()". --- .../inverseconduit/javadoc/ClassName.java | 26 ++++++++++--------- .../javadoc/JavaDocAccessor.java | 8 +++--- .../inverseconduit/javadoc/JavadocDao.java | 2 +- .../javadoc/LibraryZipFile.java | 4 +-- .../inverseconduit/javadoc/MethodInfo.java | 4 +-- .../javadoc/JavadocDaoTest.java | 8 +++--- .../javadoc/LibraryZipFileTest.java | 4 +-- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java index 0bdf60e..bd94ccb 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java @@ -5,24 +5,26 @@ * @author Michael Angstadt */ public class ClassName { - private final String full, simple; + private final String fullyQualified, simple; /** - * @param full the fully-qualified class name (e.g. "java.lang.String") + * @param fullyQualified the fully-qualified class name (e.g. + * "java.lang.String") */ - public ClassName(String full) { - this.full = full; + public ClassName(String fullyQualified) { + this.fullyQualified = fullyQualified; - int pos = full.lastIndexOf('.'); - simple = (pos < 0) ? full : full.substring(pos + 1); + int pos = fullyQualified.lastIndexOf('.'); + simple = (pos < 0) ? fullyQualified : fullyQualified.substring(pos + 1); } /** - * @param full the fully-qualified class name (e.g. "java.lang.String") + * @param fullyQualified the fully-qualified class name (e.g. + * "java.lang.String") * @param simple the simple class name (e.g. "String") */ - public ClassName(String full, String simple) { - this.full = full; + public ClassName(String fullyQualified, String simple) { + this.fullyQualified = fullyQualified; this.simple = simple; } @@ -30,8 +32,8 @@ public ClassName(String full, String simple) { * Gets the fully-qualified class name * @return the fully-qualified class name (e.g. "java.lang.String") */ - public String getFull() { - return full; + public String getFullQualified() { + return fullyQualified; } /** @@ -44,6 +46,6 @@ public String getSimple() { @Override public String toString() { - return full; + return fullyQualified; } } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 10a57e0..ccea22c 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -323,7 +323,7 @@ private String printMethodChoices(Multimap matchingMethod String signature; { StringBuilder sb = new StringBuilder(); - sb.append(classInfo.getName().getFull()).append("#").append(methodInfo.getName()); + sb.append(classInfo.getName().getFullQualified()).append("#").append(methodInfo.getName()); List paramList = new ArrayList<>(); for (ParameterInfo param : methodInfo.getParameters()) { @@ -414,7 +414,7 @@ private String printClass(ClassInfo info, int paragraph) { //print class name if (deprecated) cb.strike(); - String fullName = info.getName().getFull(); + String fullName = info.getName().getFullQualified(); String url = info.getFrameUrl(); if (url == null) { cb.bold().code(fullName).bold(); @@ -500,7 +500,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li //add parent class to the stack ClassName superClass = curInfo.getSuperClass(); if (superClass != null) { - ClassInfo superClassInfo = dao.getClassInfo(superClass.getFull()); + ClassInfo superClassInfo = dao.getClassInfo(superClass.getFullQualified()); if (superClassInfo != null) { stack.add(superClassInfo); } @@ -508,7 +508,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li //add interfaces to the stack for (ClassName interfaceName : curInfo.getInterfaces()) { - ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFull()); + ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFullQualified()); if (interfaceInfo != null) { stack.add(interfaceInfo); } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java index 1b80f12..8b64250 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java @@ -75,7 +75,7 @@ private void addApi(Path zipFile) throws IOException { synchronized (this) { while (it.hasNext()) { ClassName className = it.next(); - String fullName = className.getFull(); + String fullName = className.getFullQualified(); String simpleName = className.getSimple(); aliases.put(simpleName.toLowerCase(), fullName); diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java index b591f94..0a4a6c1 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java @@ -84,7 +84,7 @@ public String getFrameUrl(ClassInfo info) { if (baseUrl == null) { return null; } - return baseUrl + "index.html?" + info.getName().getFull().replace('.', '/') + ".html"; + return baseUrl + "index.html?" + info.getName().getFullQualified().replace('.', '/') + ".html"; } /** @@ -96,7 +96,7 @@ public String getUrl(ClassInfo info) { if (baseUrl == null) { return null; } - return baseUrl + info.getName().getFull().replace('.', '/') + ".html"; + return baseUrl + info.getName().getFullQualified().replace('.', '/') + ".html"; } /** diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java index 09c0b65..c3b9575 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java @@ -31,7 +31,7 @@ private MethodInfo(Builder builder) { sb.append(name).append('-'); List fullNames = new ArrayList<>(); for (ParameterInfo parameter : parameters) { - String fullName = parameter.getType().getFull(); + String fullName = parameter.getType().getFullQualified(); if (parameter.isArray()) { fullName += ":A"; } @@ -102,7 +102,7 @@ public boolean isDeprecated() { public String getSignature() { List params = new ArrayList<>(); for (ParameterInfo parameter : parameters) { - params.add(parameter.getType().getFull() + (parameter.isArray() ? "[]" : "")); + params.add(parameter.getType().getFullQualified() + (parameter.isArray() ? "[]" : "")); } return name + "(" + String.join(", ", params) + ")"; } diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java index c3b27b6..f0cadbf 100644 --- a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java +++ b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java @@ -48,7 +48,7 @@ public static void beforeClass() { @Test public void simpleName_single_match() throws Exception { ClassInfo info = dao.getClassInfo("Collection"); - assertEquals("java.util.Collection", info.getName().getFull()); + assertEquals("java.util.Collection", info.getName().getFullQualified()); } @Test @@ -60,7 +60,7 @@ public void simpleName_no_match() throws Exception { @Test public void simpleName_case_insensitive() throws Exception { ClassInfo info = dao.getClassInfo("collection"); - assertEquals("java.util.Collection", info.getName().getFull()); + assertEquals("java.util.Collection", info.getName().getFullQualified()); } @Test @@ -78,13 +78,13 @@ public void simpleName_multiple_matches() throws Exception { @Test public void fullName() throws Exception { ClassInfo info = dao.getClassInfo("java.util.List"); - assertEquals("java.util.List", info.getName().getFull()); + assertEquals("java.util.List", info.getName().getFullQualified()); } @Test public void fullName_case_insensitive() throws Exception { ClassInfo info = dao.getClassInfo("java.util.list"); - assertEquals("java.util.List", info.getName().getFull()); + assertEquals("java.util.List", info.getName().getFullQualified()); } @Test diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java index b68e4b5..2f70eff 100644 --- a/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java +++ b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java @@ -57,7 +57,7 @@ public void getClasses() throws Exception { Iterator it = zip.getClasses(); Set actual = new HashSet<>(); while (it.hasNext()) { - actual.add(it.next().getFull()); + actual.add(it.next().getFullQualified()); } //@formatter:off @@ -81,7 +81,7 @@ public void getClassInfo_not_found() throws Exception { @Test public void getClassInfo() throws Exception { ClassInfo info = zip.getClassInfo("java.lang.Object"); - assertEquals("java.lang.Object", info.getName().getFull()); + assertEquals("java.lang.Object", info.getName().getFullQualified()); assertEquals("Object", info.getName().getSimple()); } } From 05774766268bba8eb59368c8b2e196a4265d0550 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 10:43:42 -0500 Subject: [PATCH 05/13] Made modifier collections static. --- .../inverseconduit/javadoc/JavaDocAccessor.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index ccea22c..1a2c3b4 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -40,8 +40,8 @@ public class JavaDocAccessor { * class has multiple modifiers, I want them to be displayed in a consistent * order. */ - private final List classModifiers; - { + private static final List classModifiers; + static { ImmutableList.Builder b = new ImmutableList.Builder<>(); b.add("abstract", "final"); classModifiers = b.build(); @@ -52,8 +52,8 @@ public class JavaDocAccessor { * type, but there's no explicit check for this (things shouldn't break if a * class does not have exactly one). */ - private final Set classTypes; - { + private static final Set classTypes; + static { ImmutableSet.Builder b = new ImmutableSet.Builder<>(); b.add("annotation", "class", "enum", "exception", "interface"); classTypes = b.build(); @@ -62,8 +62,8 @@ public class JavaDocAccessor { /** * The method modifiers to ignore when outputting a method to the chat. */ - private final Set methodModifiersToIgnore; - { + private static final Set methodModifiersToIgnore; + static { ImmutableSet.Builder b = new ImmutableSet.Builder<>(); b.add("private"); b.add("protected"); From c2cccbfad7dc2be5fb7f574c54f94d33521383db Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 10:45:42 -0500 Subject: [PATCH 06/13] Renamed variable. --- .../inverseconduit/javadoc/JavaDocAccessor.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 1a2c3b4..da79a0b 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -447,11 +447,11 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li Set matchingNameSignatures = new HashSet<>(); //search the class, all its parent classes, and all its interfaces and the interfaces of its super classes - LinkedList stack = new LinkedList<>(); - stack.add(info); + LinkedList typeStack = new LinkedList<>(); + typeStack.add(info); - while (!stack.isEmpty()) { - ClassInfo curInfo = stack.removeLast(); + while (!typeStack.isEmpty()) { + ClassInfo curInfo = typeStack.removeLast(); for (MethodInfo curMethod : curInfo.getMethods()) { if (!curMethod.getName().equalsIgnoreCase(methodName)) { //name doesn't match @@ -502,7 +502,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li if (superClass != null) { ClassInfo superClassInfo = dao.getClassInfo(superClass.getFullQualified()); if (superClassInfo != null) { - stack.add(superClassInfo); + typeStack.add(superClassInfo); } } @@ -510,7 +510,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li for (ClassName interfaceName : curInfo.getInterfaces()) { ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFullQualified()); if (interfaceInfo != null) { - stack.add(interfaceInfo); + typeStack.add(interfaceInfo); } } } From 01c8aaadfae4e87641f20187b97408e5b44d31a0 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:02:29 -0500 Subject: [PATCH 07/13] Refactored to use lambdas. --- src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java index 5ac6609..ab374dd 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java +++ b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java @@ -53,12 +53,7 @@ private void processMessageQueue() { } private void processMessage(final ChatMessage chatMessage) { - for (final CommandHandle listener : listeners){ - final String response = listener.execute(chatMessage); - if (response != null){ - chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(chatMessage), response); - } - } + listeners.stream().map(l -> l.execute(chatMessage)).filter(l -> null != l).forEach(result -> chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(chatMessage), result)); final String trigger = AppContext.INSTANCE.get(BotConfig.class).getTrigger(); if ( !chatMessage.getMessage().startsWith(trigger)) { return; } From edc2e3bfe3b8137422f3f79c8baba95a30651f23 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:07:56 -0500 Subject: [PATCH 08/13] Refactored code out into a method. --- .../javadoc/JavaDocAccessor.java | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index da79a0b..16720ff 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -289,31 +289,7 @@ private String printMethodChoices(Multimap matchingMethod prevChoices = new ArrayList<>(); prevChoicesPinged = System.currentTimeMillis(); ChatBuilder cb = new ChatBuilder(); - if (matchingMethods.size() == 1) { - if (methodParams == null) { - cb.append("Did you mean this one? (type the number)"); - } else { - if (methodParams.isEmpty()) { - cb.append("I couldn't find a zero-arg signature for that method."); - } else { - cb.append("I couldn't find a signature with "); - cb.append((methodParams.size() == 1) ? "that parameter." : "those parameters."); - } - cb.append(" Did you mean this one? (type the number)"); - } - } else { - if (methodParams == null) { - cb.append("Which one do you mean? (type the number)"); - } else { - if (methodParams.isEmpty()) { - cb.append("I couldn't find a zero-arg signature for that method."); - } else { - cb.append("I couldn't find a signature with "); - cb.append((methodParams.size() == 1) ? "that parameter." : "those parameters."); - } - cb.append(" Did you mean one of these? (type the number)"); - } - } + cb.append(buildMethodChoiceQuestion(matchingMethods, methodParams)); int count = 1; for (Map.Entry entry : matchingMethods.entries()) { @@ -341,6 +317,39 @@ private String printMethodChoices(Multimap matchingMethod return cb.toString(); } + private String buildMethodChoiceQuestion(Multimap matchingMethods, List methodParams) { + if (matchingMethods.size() == 1) { + if (methodParams == null) { + return "Did you mean this one? (type the number)"; + } + + if (methodParams.isEmpty()) { + return "I couldn't find a zero-arg signature for that method."; + } + + //@formatter:off + return + "I couldn't find a signature with " + + ((methodParams.size() == 1) ? "that parameter." : "those parameters.") + + " Did you mean this one? (type the number)"; + //@formatter:on + } + + if (methodParams == null) { + return "Which one do you mean? (type the number)"; + } + + StringBuilder sb = new StringBuilder(); + if (methodParams.isEmpty()) { + sb.append("I couldn't find a zero-arg signature for that method."); + } else { + sb.append("I couldn't find a signature with "); + sb.append((methodParams.size() == 1) ? "that parameter." : "those parameters."); + } + sb.append(" Did you mean one of these? (type the number)"); + return sb.toString(); + } + /** * Prints the classes to choose from when multiple class are found. * @param classes the fully-qualified names of the classes From f115c3a7606e4a69716bbff9932ffbf0d5fa30c8 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:09:54 -0500 Subject: [PATCH 09/13] Put class fields on separate lines. :P --- .../java/com/gmail/inverseconduit/javadoc/ClassInfo.java | 6 ++++-- .../java/com/gmail/inverseconduit/javadoc/ClassName.java | 3 ++- .../com/gmail/inverseconduit/javadoc/LibraryZipFile.java | 5 ++++- .../java/com/gmail/inverseconduit/javadoc/MethodInfo.java | 4 +++- .../com/gmail/inverseconduit/javadoc/ParameterInfo.java | 6 ++++-- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java index 90c6369..baf8812 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassInfo.java @@ -13,7 +13,8 @@ * @author Michael Angstadt */ public class ClassInfo { - private final ClassName name, superClass; + private final ClassName name; + private final ClassName superClass; private final String description; private final Set modifiers; private final Set interfaces; @@ -125,7 +126,8 @@ public LibraryZipFile getZipFile() { * Builds new instances of {@link ClassInfo}. */ public static class Builder { - private ClassName name, superClass; + private ClassName name; + private ClassName superClass; private String description; private ImmutableSet.Builder modifiers = ImmutableSet.builder(); private ImmutableSet.Builder interfaces = ImmutableSet.builder(); diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java index bd94ccb..10c6bcb 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java @@ -5,7 +5,8 @@ * @author Michael Angstadt */ public class ClassName { - private final String fullyQualified, simple; + private final String fullyQualified; + private final String simple; /** * @param fullyQualified the fully-qualified class name (e.g. diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java index 0a4a6c1..333ad29 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java @@ -28,7 +28,10 @@ public class LibraryZipFile { private static final String infoFileName = "info" + extension; private final Path file; - private final String baseUrl, name, version, projectUrl; + private final String baseUrl; + private final String name; + private final String version; + private final String projectUrl; public LibraryZipFile(Path file) throws IOException { this.file = file.toRealPath(); diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java index c3b9575..54a76c6 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java @@ -13,7 +13,9 @@ * @author Michael Angstadt */ public class MethodInfo { - private final String name, description, urlAnchor; + private final String name; + private final String description; + private final String urlAnchor; private final Set modifiers; private final List parameters; private final ClassName returnValue; diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java index 2028040..7899e75 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ParameterInfo.java @@ -6,8 +6,10 @@ */ public class ParameterInfo { private final ClassName type; - private final String name, generic; - private final boolean array, varargs; + private final String name; + private final String generic; + private final boolean array; + private final boolean varargs; public ParameterInfo(ClassName type, String name, boolean array, boolean varargs, String generic) { this.type = type; From 036e7d9b06b7e3933ab3bbf51352a59671b4e809 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:12:04 -0500 Subject: [PATCH 10/13] Changed the name of static constants to uppercase. --- .../inverseconduit/javadoc/LibraryZipFile.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java index 333ad29..7962465 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java @@ -24,8 +24,8 @@ * @author Michael Angstadt */ public class LibraryZipFile { - private static final String extension = ".xml"; - private static final String infoFileName = "info" + extension; + private static final String EXTENSION = ".xml"; + private static final String INFO_FILENAME = "info" + EXTENSION; private final Path file; private final String baseUrl; @@ -37,7 +37,7 @@ public LibraryZipFile(Path file) throws IOException { this.file = file.toRealPath(); try (FileSystem fs = FileSystems.newFileSystem(file, null)) { - Path info = fs.getPath("/" + infoFileName); + Path info = fs.getPath("/" + INFO_FILENAME); if (!Files.exists(info)) { baseUrl = name = version = projectUrl = null; return; @@ -111,11 +111,11 @@ public Iterator getClasses() throws IOException { final FileSystem fs = FileSystems.newFileSystem(file, null); final DirectoryStream stream = Files.newDirectoryStream(fs.getPath("/"), entry -> { String name = entry.getFileName().toString(); - if (!name.endsWith(extension)) { + if (!name.endsWith(EXTENSION)) { return false; } - return !name.equals(infoFileName); + return !name.equals(INFO_FILENAME); }); final Iterator it = stream.iterator(); @@ -143,7 +143,7 @@ public boolean hasNext() { public ClassName next() { Path file = it.next(); String fileName = file.getFileName().toString(); - String fullName = fileName.substring(0, fileName.length() - extension.length()); + String fullName = fileName.substring(0, fileName.length() - EXTENSION.length()); return new ClassName(fullName); } }; @@ -158,7 +158,7 @@ public ClassName next() { */ public ClassInfo getClassInfo(String fullName) throws IOException { try (FileSystem fs = FileSystems.newFileSystem(file, null)) { - Path path = fs.getPath(fullName + extension); + Path path = fs.getPath(fullName + EXTENSION); if (!Files.exists(path)) { return null; } From 1e80b940a305c525cd22f2c28dd719f5deaf58e2 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:30:16 -0500 Subject: [PATCH 11/13] Fixed method name. --- .../java/com/gmail/inverseconduit/javadoc/ClassName.java | 2 +- .../com/gmail/inverseconduit/javadoc/JavaDocAccessor.java | 8 ++++---- .../java/com/gmail/inverseconduit/javadoc/JavadocDao.java | 2 +- .../com/gmail/inverseconduit/javadoc/LibraryZipFile.java | 4 ++-- .../java/com/gmail/inverseconduit/javadoc/MethodInfo.java | 4 ++-- .../com/gmail/inverseconduit/javadoc/JavadocDaoTest.java | 8 ++++---- .../gmail/inverseconduit/javadoc/LibraryZipFileTest.java | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java index 10c6bcb..0d2d2ef 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/ClassName.java @@ -33,7 +33,7 @@ public ClassName(String fullyQualified, String simple) { * Gets the fully-qualified class name * @return the fully-qualified class name (e.g. "java.lang.String") */ - public String getFullQualified() { + public String getFullyQualified() { return fullyQualified; } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 16720ff..1daef28 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -299,7 +299,7 @@ private String printMethodChoices(Multimap matchingMethod String signature; { StringBuilder sb = new StringBuilder(); - sb.append(classInfo.getName().getFullQualified()).append("#").append(methodInfo.getName()); + sb.append(classInfo.getName().getFullyQualified()).append("#").append(methodInfo.getName()); List paramList = new ArrayList<>(); for (ParameterInfo param : methodInfo.getParameters()) { @@ -423,7 +423,7 @@ private String printClass(ClassInfo info, int paragraph) { //print class name if (deprecated) cb.strike(); - String fullName = info.getName().getFullQualified(); + String fullName = info.getName().getFullyQualified(); String url = info.getFrameUrl(); if (url == null) { cb.bold().code(fullName).bold(); @@ -509,7 +509,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li //add parent class to the stack ClassName superClass = curInfo.getSuperClass(); if (superClass != null) { - ClassInfo superClassInfo = dao.getClassInfo(superClass.getFullQualified()); + ClassInfo superClassInfo = dao.getClassInfo(superClass.getFullyQualified()); if (superClassInfo != null) { typeStack.add(superClassInfo); } @@ -517,7 +517,7 @@ private MatchingMethods getMatchingMethods(ClassInfo info, String methodName, Li //add interfaces to the stack for (ClassName interfaceName : curInfo.getInterfaces()) { - ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFullQualified()); + ClassInfo interfaceInfo = dao.getClassInfo(interfaceName.getFullyQualified()); if (interfaceInfo != null) { typeStack.add(interfaceInfo); } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java index 8b64250..b278c5f 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavadocDao.java @@ -75,7 +75,7 @@ private void addApi(Path zipFile) throws IOException { synchronized (this) { while (it.hasNext()) { ClassName className = it.next(); - String fullName = className.getFullQualified(); + String fullName = className.getFullyQualified(); String simpleName = className.getSimple(); aliases.put(simpleName.toLowerCase(), fullName); diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java index 7962465..0a9dd57 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/LibraryZipFile.java @@ -87,7 +87,7 @@ public String getFrameUrl(ClassInfo info) { if (baseUrl == null) { return null; } - return baseUrl + "index.html?" + info.getName().getFullQualified().replace('.', '/') + ".html"; + return baseUrl + "index.html?" + info.getName().getFullyQualified().replace('.', '/') + ".html"; } /** @@ -99,7 +99,7 @@ public String getUrl(ClassInfo info) { if (baseUrl == null) { return null; } - return baseUrl + info.getName().getFullQualified().replace('.', '/') + ".html"; + return baseUrl + info.getName().getFullyQualified().replace('.', '/') + ".html"; } /** diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java index 54a76c6..50a5aa3 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/MethodInfo.java @@ -33,7 +33,7 @@ private MethodInfo(Builder builder) { sb.append(name).append('-'); List fullNames = new ArrayList<>(); for (ParameterInfo parameter : parameters) { - String fullName = parameter.getType().getFullQualified(); + String fullName = parameter.getType().getFullyQualified(); if (parameter.isArray()) { fullName += ":A"; } @@ -104,7 +104,7 @@ public boolean isDeprecated() { public String getSignature() { List params = new ArrayList<>(); for (ParameterInfo parameter : parameters) { - params.add(parameter.getType().getFullQualified() + (parameter.isArray() ? "[]" : "")); + params.add(parameter.getType().getFullyQualified() + (parameter.isArray() ? "[]" : "")); } return name + "(" + String.join(", ", params) + ")"; } diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java index f0cadbf..184b701 100644 --- a/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java +++ b/src/test/java/com/gmail/inverseconduit/javadoc/JavadocDaoTest.java @@ -48,7 +48,7 @@ public static void beforeClass() { @Test public void simpleName_single_match() throws Exception { ClassInfo info = dao.getClassInfo("Collection"); - assertEquals("java.util.Collection", info.getName().getFullQualified()); + assertEquals("java.util.Collection", info.getName().getFullyQualified()); } @Test @@ -60,7 +60,7 @@ public void simpleName_no_match() throws Exception { @Test public void simpleName_case_insensitive() throws Exception { ClassInfo info = dao.getClassInfo("collection"); - assertEquals("java.util.Collection", info.getName().getFullQualified()); + assertEquals("java.util.Collection", info.getName().getFullyQualified()); } @Test @@ -78,13 +78,13 @@ public void simpleName_multiple_matches() throws Exception { @Test public void fullName() throws Exception { ClassInfo info = dao.getClassInfo("java.util.List"); - assertEquals("java.util.List", info.getName().getFullQualified()); + assertEquals("java.util.List", info.getName().getFullyQualified()); } @Test public void fullName_case_insensitive() throws Exception { ClassInfo info = dao.getClassInfo("java.util.list"); - assertEquals("java.util.List", info.getName().getFullQualified()); + assertEquals("java.util.List", info.getName().getFullyQualified()); } @Test diff --git a/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java index 2f70eff..6f5a60a 100644 --- a/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java +++ b/src/test/java/com/gmail/inverseconduit/javadoc/LibraryZipFileTest.java @@ -57,7 +57,7 @@ public void getClasses() throws Exception { Iterator it = zip.getClasses(); Set actual = new HashSet<>(); while (it.hasNext()) { - actual.add(it.next().getFullQualified()); + actual.add(it.next().getFullyQualified()); } //@formatter:off @@ -81,7 +81,7 @@ public void getClassInfo_not_found() throws Exception { @Test public void getClassInfo() throws Exception { ClassInfo info = zip.getClassInfo("java.lang.Object"); - assertEquals("java.lang.Object", info.getName().getFullQualified()); + assertEquals("java.lang.Object", info.getName().getFullyQualified()); assertEquals("Object", info.getName().getSimple()); } } From 2abd9b7fc568f08649c2b501247ef7a2f8b6b4a9 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sat, 24 Jan 2015 11:39:45 -0500 Subject: [PATCH 12/13] Put all items in the same method call. --- .../com/gmail/inverseconduit/javadoc/JavaDocAccessor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 1daef28..f3eb22f 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -31,6 +31,9 @@ public class JavaDocAccessor { private final JavadocDao dao; + /** + * + */ private List prevChoices = new ArrayList<>(); private long prevChoicesPinged = 0; private final long choiceTimeout = TimeUnit.SECONDS.toMillis(30); @@ -65,9 +68,7 @@ public class JavaDocAccessor { private static final Set methodModifiersToIgnore; static { ImmutableSet.Builder b = new ImmutableSet.Builder<>(); - b.add("private"); - b.add("protected"); - b.add("public"); + b.add("private", "protected", "public"); methodModifiersToIgnore = b.build(); } From c3fb6612f71485eac440d9321f4ef3f46d2dcb96 Mon Sep 17 00:00:00 2001 From: Michael Angstadt Date: Sun, 25 Jan 2015 09:05:44 -0500 Subject: [PATCH 13/13] Refactored long method into multiple methods. --- .../javadoc/JavaDocAccessor.java | 131 ++++++++++++------ 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index f3eb22f..6050e1f 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -29,14 +29,27 @@ * presents them with a list of choices. */ public class JavaDocAccessor { + /** + * Used for accessing the Javadoc information. + */ private final JavadocDao dao; /** - * + * The most recent list of suggestions that were sent to the chat. */ private List prevChoices = new ArrayList<>(); + + /** + * The last time the list of previous choices were accessed in some way + * (timestamp). + */ private long prevChoicesPinged = 0; - private final long choiceTimeout = TimeUnit.SECONDS.toMillis(30); + + /** + * Stop responding to numeric choices the user enters after this amount of + * time. + */ + private static final long choiceTimeout = TimeUnit.SECONDS.toMillis(30); /** * "Flags" that a class can have. They are defined in a List because, if a @@ -106,52 +119,10 @@ private String generateResponse(String commandTextStr) { ClassInfo info; try { info = dao.getClassInfo(commandText.className); - } catch (MultipleClassesFoundException e) { - if (commandText.methodName == null) { - return printClassChoices(e.getClasses()); - } - - //search each class for a method that matches the given signature - Map exactMatches = new HashMap<>(); - Multimap matchingNames = ArrayListMultimap.create(); - for (String className : e.getClasses()) { - try { - ClassInfo classInfo = dao.getClassInfo(className); - MatchingMethods methods = getMatchingMethods(classInfo, commandText.methodName, commandText.parameters); - if (methods.exactSignature != null) { - exactMatches.put(classInfo, methods.exactSignature); - } - matchingNames.putAll(classInfo, methods.matchingName); - } catch (IOException e2) { - throw new RuntimeException("Problem getting Javadoc info.", e2); - } - } - - if (exactMatches.isEmpty() && matchingNames.isEmpty()) { - //no matches found - return "Sorry, I can't find that method. :("; - } - - if (exactMatches.size() == 1) { - //a single, exact match was found! - MethodInfo method = exactMatches.values().iterator().next(); - ClassInfo classInfo = exactMatches.keySet().iterator().next(); - return printMethod(method, classInfo, commandText.paragraph); - } - - //multiple matches were found - Multimap map; - if (exactMatches.size() > 1) { - map = ArrayListMultimap.create(); - for (Map.Entry entry : exactMatches.entrySet()) { - map.put(entry.getKey(), entry.getValue()); - } - } else { - map = matchingNames; - } - return printMethodChoices(map, commandText.parameters); } catch (IOException e) { throw new RuntimeException("Problem getting Javadoc info.", e); + } catch (MultipleClassesFoundException e) { + return handleMultipleMatches(commandText, e.getClasses()); } if (info == null) { @@ -159,6 +130,17 @@ private String generateResponse(String commandTextStr) { return "Sorry, I never heard of that class. :("; } + return handleSingleMatch(commandText, info); + } + + /** + * Generates the chat response for when the user's query returns a single + * class. + * @param commandText the command text + * @param info the class that was found + * @return the chat response + */ + private String handleSingleMatch(CommandTextParser commandText, ClassInfo info) { if (commandText.methodName == null) { //method name not specified, so print the class docs return printClass(info, commandText.paragraph); @@ -192,6 +174,63 @@ private String generateResponse(String commandTextStr) { return printMethodChoices(map, commandText.parameters); } + /** + * Generates the chat response for when the user's query returns more than + * one class. + * @param commandText the command text + * @param matches the fully-qualified names of the classes that were found + * @return the chat response + */ + private String handleMultipleMatches(CommandTextParser commandText, Collection matches) { + if (commandText.methodName == null) { + //just print the class choices, since the user did not specify a method + return printClassChoices(matches); + } + + //search each class for a method that matches the given signature + Map exactMatches = new HashMap<>(); + Multimap matchingNames = ArrayListMultimap.create(); + for (String className : matches) { + ClassInfo classInfo; + MatchingMethods methods; + try { + classInfo = dao.getClassInfo(className); + methods = getMatchingMethods(classInfo, commandText.methodName, commandText.parameters); + } catch (IOException e) { + throw new RuntimeException("Problem getting Javadoc info.", e); + } + + if (methods.exactSignature != null) { + exactMatches.put(classInfo, methods.exactSignature); + } + matchingNames.putAll(classInfo, methods.matchingName); + } + + if (exactMatches.isEmpty() && matchingNames.isEmpty()) { + //no matches found + return "Sorry, I can't find that method. :("; + } + + if (exactMatches.size() == 1) { + //a single, exact match was found! + MethodInfo method = exactMatches.values().iterator().next(); + ClassInfo classInfo = exactMatches.keySet().iterator().next(); + return printMethod(method, classInfo, commandText.paragraph); + } + + //multiple matches were found + Multimap choicesToPrint; + if (exactMatches.size() > 1) { + choicesToPrint = ArrayListMultimap.create(); + for (Map.Entry entry : exactMatches.entrySet()) { + choicesToPrint.put(entry.getKey(), entry.getValue()); + } + } else { + choicesToPrint = matchingNames; + } + return printMethodChoices(choicesToPrint, commandText.parameters); + } + /** * Called when someone types a number into the chat, in response to the * javadoc command showing a list of choices.
- - - - - - - -
Java™ Platform
Standard Ed. 8
-