Java tutorial
/* * Copyright (c) 2008, 2015, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - Neither the name of Oracle Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package ensemble.compiletime.search; import ensemble.compiletime.Sample; import java.io.*; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.util.Version; /** * Generate the lucene index that Ensemble uses for its search */ public class BuildEnsembleSearchIndex { public static void buildSearchIndex(List<Sample> allSamples, String javaDocBaseUrl, String javafxDocumentationHome, File indexDir) { try { List<Document> docs = new ArrayList<>(); List<Callable<List<Document>>> tasks = new ArrayList<>(); // create callables to collect data System.out.println("Creating Documents for Samples..."); docs.addAll(indexSamples(allSamples)); System.out.println("Creating tasks for getting all documentation..."); tasks.addAll(indexJavaDocAllClasses(javaDocBaseUrl)); tasks.addAll(indexAllDocumentation(javafxDocumentationHome)); // execute all the tasks in 32 threads, collecting all the documents to write System.out.println("Executing tasks getting all documentation..."); try { ThreadPoolExecutor executor = new ThreadPoolExecutor(32, 32, 30, TimeUnit.SECONDS, new LinkedBlockingQueue()); executor.setThreadFactory(new ThreadFactory() { int index = 0; @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "Thread-" + (++index)); thread.setDaemon(true); return thread; } }); List<Future<List<Document>>> results = executor.invokeAll(tasks); for (Future<List<Document>> future : results) { docs.addAll(future.get()); } } catch (ExecutionException | InterruptedException ex) { Logger.getLogger(BuildEnsembleSearchIndex.class.getName()).log(Level.SEVERE, null, ex); } // create index System.out.println("Indexing to directory '" + indexDir + "'..."); Directory dir = FSDirectory.open(indexDir); Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_31); IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_31, analyzer); iwc.setOpenMode(OpenMode.CREATE); try (IndexWriter writer = new IndexWriter(dir, iwc)) { // write all docs System.out.println("Writing [" + docs.size() + "] documents to index...."); writer.addDocuments(docs); // optimize the writen index System.out.println("Optimizing search index...."); writer.optimize(); System.out.println("NUMBER OF INDEXED DOCUMENTS = [" + writer.numDocs() + "]"); } // write file listing all the search index files, so we know what // is in the jar file at runtime try (FileWriter listAllOut = new FileWriter(new File(indexDir, "listAll.txt"))) { for (String fileName : dir.listAll()) { if (!"listAll.txt".equals(fileName)) { // don't include the "listAll.txt" file Long length = dir.fileLength(fileName); listAllOut.write(fileName); listAllOut.write(':'); listAllOut.write(length.toString()); listAllOut.write('\n'); } } listAllOut.flush(); } System.out.println("Finished writing search index to directory '" + indexDir); } catch (IOException ex) { Logger.getLogger(BuildEnsembleSearchIndex.class.getName()).log(Level.SEVERE, null, ex); } } private static List<Callable<List<Document>>> indexAllDocumentation(String javafxDocumentationHome) throws IOException { List<Callable<List<Document>>> tasks = new ArrayList<>(); CharSequence content = grabWebPage(javafxDocumentationHome); String baseUrl = javafxDocumentationHome.substring(0, javafxDocumentationHome.lastIndexOf('/') + 1); // System.out.println("baseUrl = " + baseUrl); // parse page finding all docs pages Matcher matcher = docsHomeLink.matcher(content); System.out.println("Building a list of documentation to index"); while (matcher.find()) { String foundUrl = matcher.group(1); // System.out.println("foundUrl = " + foundUrl); final String docPageUrl = (foundUrl.startsWith("http") ? foundUrl : baseUrl + foundUrl); if ("https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html".equals(docPageUrl) || "https://docs.oracle.com/javafx/2/api/index.html".equals(docPageUrl) || "http://www.oracle.com/technetwork/java/javafx/downloads/supportedconfigurations-1506746.html" .equals(docPageUrl) || "http://www.oracle.com/technetwork/java/javase/downloads/".equals(docPageUrl) || "https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html" .equals(docPageUrl)) { continue; } System.out.println(docPageUrl); tasks.add((Callable<List<Document>>) () -> indexDocumentationPage(docPageUrl)); } System.out.println(" --- end of list ---"); return tasks; } private static List<Document> indexDocumentationPage(String docPageUrl) throws IOException { List<Document> docs = new ArrayList<>(); try { // System.out.println("PROCESSING... ["+docPageUrl+"] on Thread ["+Thread.currentThread().getName()+"]"); // System.out.println("=================================================================="); // System.out.println("Parsing docs page ["+docPageUrl+"] ..."); DocumentationIndexer.DocPage docPage = DocumentationIndexer.parseDocsPage(docPageUrl, grabWebPage(docPageUrl).toString()); // System.out.println("TITLE="+docPage.bookTitle+" CHAPTER="+docPage.chapter+" SECTIONS=["+docPage.sections.size()+"]"); for (DocumentationIndexer.Section section : docPage.sections) { if (section.name == null) { System.out.println("section.name = " + section.name + " docPage.bookTitle=" + docPage.bookTitle + " " + docPageUrl); } // write documentation section entry to index docs.add(createDocument(DocumentType.DOC, new Field("bookTitle", docPage.bookTitle, Field.Store.YES, Field.Index.ANALYZED), new Field("chapter", docPage.chapter == null ? "" : docPage.chapter, Field.Store.YES, Field.Index.ANALYZED), new Field("name", section.name, Field.Store.YES, Field.Index.ANALYZED), new Field("description", section.content, Field.Store.NO, Field.Index.ANALYZED), new Field("ensemblePath", section.url, Field.Store.YES, Field.Index.NOT_ANALYZED))); } // handle next page if there is one if (docPage.nextUrl != null) { docs.addAll(indexDocumentationPage(docPage.nextUrl)); } } catch (Exception ex) { System.out.println("FAILED TO PARSE DOCS PAGE SO IGNORED: [" + docPageUrl + "]"); ex.printStackTrace(System.out); } return docs; } private static List<Callable<List<Document>>> indexJavaDocAllClasses(final String javaDocBaseUrl) throws IOException { CharSequence content = grabWebPage(javaDocBaseUrl + "allclasses-noframe.html"); List<Callable<List<Document>>> tasks = new ArrayList<>(); // parse package Matcher matcher = findClassUrl.matcher(content); while (matcher.find()) { final String classUrl = javaDocBaseUrl + matcher.group(1); tasks.add((Callable<List<Document>>) () -> indexApiDocs(classUrl)); } return tasks; } /** * Add all samples to the search index */ private static List<Document> indexSamples(List<Sample> allSamples) throws IOException { List<Document> docs = new ArrayList<>(); for (Sample sample : allSamples) { // write class entry to index docs.add(createDocument(DocumentType.SAMPLE, new Field("name", sample.name, Field.Store.YES, Field.Index.ANALYZED), new Field("description", sample.description, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", sample.description.substring(0, Math.min(160, sample.description.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("ensemblePath", "sample://" + sample.ensemblePath, Field.Store.YES, Field.Index.NOT_ANALYZED))); } return docs; } /** * Index a JavaDoc page for a single class, interface or enum * * @param writer The index writer to add documents to * @param url The url to the javadoc html file * @throws IOException If there was a problem indexing the file */ private static List<Document> indexApiDocs(String url) throws IOException { // System.out.println("PROCESSING... ["+url+"] on Thread ["+Thread.currentThread().getName()+"]"); final List<Document> docs = new ArrayList<>(); CharSequence content = grabWebPage(url); // extract package and class Matcher packageAndClassMatcher = PACKAGE_AND_CLASS.matcher(content); // search and if we fail to find ignore this file if (!packageAndClassMatcher.find()) { //System.out.println("!!!! Ignoring [" + file + "] because no class or package was found"); return docs; } else { //System.out.println("Adding [" + file + "]"); } //System.out.println(" fileUrl = " + fileUrl); String packageName = packageAndClassMatcher.group(1); //System.out.println(" packageName = " + packageName); String classType = packageAndClassMatcher.group(2).toLowerCase(); //System.out.println(" classType = " + classType); String className = packageAndClassMatcher.group(3); //System.out.println(" className = " + className); // extract document type DocumentType documentType = DocumentType.CLASS; if ("enum".equals(classType)) { documentType = DocumentType.ENUM; } // extract javadoc description Matcher classDescriptionMatcher = CLASS_DESCRIPTION.matcher(content); String classDescription = ""; if (classDescriptionMatcher.find()) { classDescription = cleanHTML(classDescriptionMatcher.group(1)); } ///System.out.println("classDescription = " + classDescription); // write class entry to index docs.add(createDocument(documentType, new Field("name", className, Field.Store.YES, Field.Index.ANALYZED), new Field("description", classDescription, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", classDescription.substring(0, Math.min(160, classDescription.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("package", packageName, Field.Store.YES, Field.Index.ANALYZED), new Field("url", url, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("ensemblePath", url, Field.Store.YES, Field.Index.NOT_ANALYZED) // TODO what do we need here )); // extract properties Matcher propertySummaryMatcher = PROPERTY_SUMMARY.matcher(content); if (propertySummaryMatcher.find()) { String propertySummaryTable = propertySummaryMatcher.group(1); Matcher propertyMatcher = PROPERTY.matcher(propertySummaryTable); while (propertyMatcher.find()) { String propUrl = propertyMatcher.group(1); String propertyName = propertyMatcher.group(2); String description = cleanHTML(propertyMatcher.group(3)); //System.out.println(" propertyName = " + propertyName); //System.out.println(" description = " + description); //System.out.println(" url = " + url); propUrl = url + "#" + propertyName; //System.out.println(" oracle url = " + url); // write class entry to index docs.add(createDocument(DocumentType.PROPERTY, new Field("name", propertyName, Field.Store.YES, Field.Index.ANALYZED), new Field("description", description, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", description.substring(0, Math.min(160, description.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("url", propUrl, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("className", className, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("package", packageName, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field( "ensemblePath", url + "#" + propertyName, Field.Store.YES, Field.Index.NOT_ANALYZED) // TODO what do we need here )); } } // extract methods Matcher methodSummaryMatcher = METHOD_SUMMARY.matcher(content); if (methodSummaryMatcher.find()) { String methodSummaryTable = methodSummaryMatcher.group(1); Matcher methodMatcher = PROPERTY.matcher(methodSummaryTable); while (methodMatcher.find()) { String methodUrl = methodMatcher.group(1); String methodName = methodMatcher.group(2); String description = cleanHTML(methodMatcher.group(3)); //System.out.println(" methodName = " + methodName); //System.out.println(" description = " + description); //System.out.println(" url = " + url); methodUrl = url + "#" + methodName + "()"; //System.out.println(" oracle url = " + url); // write class entry to index docs.add(createDocument(DocumentType.METHOD, new Field("name", methodName, Field.Store.YES, Field.Index.ANALYZED), new Field("description", description, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", description.substring(0, Math.min(160, description.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("url", methodUrl, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("className", className, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("package", packageName, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("ensemblePath", url + "#" + methodName + "()", Field.Store.YES, Field.Index.NOT_ANALYZED) // TODO what do we need here )); } } // extract fields Matcher fieldSummaryMatcher = FIELD_SUMMARY.matcher(content); if (fieldSummaryMatcher.find()) { String fieldSummaryTable = fieldSummaryMatcher.group(1); Matcher fieldMatcher = PROPERTY.matcher(fieldSummaryTable); while (fieldMatcher.find()) { String fieldUrl = fieldMatcher.group(1); String fieldName = fieldMatcher.group(2); String description = cleanHTML(fieldMatcher.group(3)); //System.out.println(" ##### fieldName = " + fieldName); //System.out.println(" description = " + description); //System.out.println(" url = " + url); fieldUrl = url + "#" + fieldName; //System.out.println(" oracle url = " + url); // write class entry to index docs.add(createDocument(DocumentType.FIELD, new Field("name", fieldName, Field.Store.YES, Field.Index.ANALYZED), new Field("description", description, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", description.substring(0, Math.min(160, description.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("url", fieldUrl, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("className", className, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("package", packageName, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("ensemblePath", url + "#" + fieldName, Field.Store.YES, Field.Index.NOT_ANALYZED) // TODO what do we need here )); } } // extract enums Matcher enumSummaryMatcher = ENUM_SUMMARY.matcher(content); if (enumSummaryMatcher.find()) { String enumSummaryTable = enumSummaryMatcher.group(1); Matcher enumMatcher = PROPERTY.matcher(enumSummaryTable); while (enumMatcher.find()) { String enumUrl = enumMatcher.group(1); String enumName = enumMatcher.group(2); String description = cleanHTML(enumMatcher.group(3)); //System.out.println(" enumName = " + enumName); //System.out.println(" description = " + description); //System.out.println(" url = " + url); enumUrl = url + "#" + enumName; ///System.out.println(" oracle url = " + url); // write class entry to index docs.add(createDocument(DocumentType.ENUM, new Field("name", enumName, Field.Store.YES, Field.Index.ANALYZED), new Field("description", description, Field.Store.NO, Field.Index.ANALYZED), new Field("shortDescription", description.substring(0, Math.min(160, description.length())), Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("url", enumUrl, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("className", className, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("package", packageName, Field.Store.YES, Field.Index.NOT_ANALYZED), new Field("ensemblePath", url + "#" + enumName, Field.Store.YES, Field.Index.NOT_ANALYZED) // TODO what do we need here )); } } return docs; } /** * Create a new document * * @param documentType The document type to save in the doc * @param fields The searchable and data fields to write into doc * @throws IOException If there was problem writing doc */ private static Document createDocument(DocumentType documentType, Field... fields) throws IOException { // make a new, empty document Document doc = new Document(); // add doc type field doc.add(new Field("documentType", documentType.toString(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); // add other fields if (fields != null) { for (Field field : fields) { doc.add(field); } } return doc; } /** * Create a new document and write it to the given writer * * @param writer The writer to write out to * @param documentType The document type to save in the doc * @param fields The searchable and data fields to write into doc * @throws IOException If there was problem writing doc */ private static void addDocument(IndexWriter writer, DocumentType documentType, Field... fields) throws IOException { // make a new, empty document Document doc = new Document(); // add doc type field doc.add(new Field("documentType", documentType.toString(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); // add other fields if (fields != null) { for (Field field : fields) { doc.add(field); } } // write into index, assuming we are recreating every time writer.addDocument(doc); } /** * Clean HTML, removing all tags and un-escaping so that we can index it cleanly * * @param html The html to clean * @return cleaned html */ private static String cleanHTML(String html) { html = html.replaceAll("( |\\s|[ ])+", " ").trim(); // cleanup whitespace html = html.replaceAll("<.*?>", " "); // remove html tags html = html.replaceAll("<", "<"); // un-escape < html = html.replaceAll(">", ">"); // un-escape > html = html.replaceAll(""", "\""); // un-escape " html = html.replaceAll("'", "\'"); // un-escape ' html = html.replaceAll("&", "&"); // un-escape & return html; } static CharSequence grabWebPage(String url) throws IOException { StringBuilder builder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) { String line; while ((line = reader.readLine()) != null) { builder.append(line); builder.append('\n'); } } return builder; } // =================== JAVAFX DOCUMENTATION PATTERNS ====================== /* GET ALL LINKS FROM DOCS HOME PAGE <p class="fxblurblink"><a href="2/overview/jfxpub-overview.htm">What is JavaFX?</a></p> GROUP 1 = url */ private static final Pattern docsHomeLink = Pattern .compile("<p\\s+class=\\\"fxblurblink\\\"\\s*>.*<a\\s*href=\\\"([^\\\"]+)"); /* GET ALL LINKS FROM DOCS HOME PAGE <div id="bookTitle"> <h1>Working With Layouts in JavaFX </h1> </div> GROUP 1 = book title */ private static final Pattern bookTitle = Pattern.compile("<div\\s+id=\\\"bookTitle\\\"\\s*>\\s*<h1>([^<]+)"); /* GET ALL LINKS FROM DOCS HOME PAGE <h1 class="chapter">JavaFX Scene Builder Overview</h1> GROUP 1 = chapter name */ private static final Pattern chapter = Pattern.compile("<h1\\s+class=\\\"chapter\\\"\\s*>([^<]+)"); // =================== API DOC PATTERNS =================================== /* Pull class urls from all classes page */ private static final Pattern findClassUrl = Pattern.compile("a\\s+href=\\\"([^\\\"]+)\\\""); /* <div class="subTitle">javafx.scene</div> <h2 title="Class Scene" class="title">Class Scene</h2> </div> GROUP 1 = Package GROUP 2 = Class Type GROUP 3 = Class */ //private static Pattern PACKAGE_AND_CLASS = Pattern.compile("<H2>\\s*<FONT SIZE=\"-1\">\\s*([^<]+)</FONT>\\s*<BR>\\s*(Class|Interface|Enum) ([^<&]+).*?</H2>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern PACKAGE_AND_CLASS = Pattern.compile( "<div class=\"subTitle\">\\s*([^<]+)</div>\\s*<h2 title=\"(Class|Interface|Enum) ([^<&]+).*?\"\\sclass=\"title\">(Class|Interface|Enum) ([^<&]+).*?</h2>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* </PRE> <P> ... <HR> <div class="description"> ... <p> ... </div> GROUP 1 = Class JavaDoc Description */ //private static Pattern CLASS_DESCRIPTION = Pattern.compile("</PRE>\\s*<P>(.*?)<HR>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern CLASS_DESCRIPTION = Pattern.compile("<div class=\"description\">.*?<[pP]>(.*?)</div>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* <table ...> ... </table> GROUP 1 = Property Summary Table */ // private static Pattern PROPERTY_SUMMARY = Pattern.compile("NAME=\"property_summary\">.*?<TABLE[^>]+>(.*?)</TABLE>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern PROPERTY_SUMMARY = Pattern.compile( "<h3>Property Summary</h3>.*?<table[^>]+>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* <h3>Method Summary</h3> <table...> ... </table> GROUP 1 = Method Summary Table */ // private static Pattern METHOD_SUMMARY = Pattern.compile("NAME=\"method_summary\">.*?<TABLE[^>]+>(.*?)</TABLE>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern METHOD_SUMMARY = Pattern.compile("<h3>Method Summary</h3>.*?<table[^>]+>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* <h3>Enum Constant Summary</h3> <table...> ... </table> GROUP 1 = Enum Summary Table */ // private static Pattern ENUM_SUMMARY = Pattern.compile("NAME=\"enum_constant_summary\">.*?<TABLE[^>]+>(.*?)</TABLE>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern ENUM_SUMMARY = Pattern.compile( "<h3>Enum Constant Summary</h3>.*?<table[^>]+>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* <h3>Field Summary</h3> <table...> ... </table> GROUP 1 = Field Summary Table */ // private static Pattern FIELD_SUMMARY = Pattern.compile("NAME=\"field_summary\">.*?<TABLE[^>]+>(.*?)</TABLE>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern FIELD_SUMMARY = Pattern.compile("<h3>Field Summary</h3>.*?<table[^>]+>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /* <td class="colFirst"><code><a href="../../../javafx/beans/property/DoubleProperty.html" title="class in javafx.beans.property">DoubleProperty</a></code></td> GROUP 1 = Url GROUP 2 = Name GROUP 2 = Description */ //private static Pattern PROPERTY = Pattern.compile("<TD>.*?<A HREF=\"([^\"]*)\">([^<]*)</A>.*?<BR>(.*?)</TD>",Pattern.DOTALL | Pattern.CASE_INSENSITIVE); private static Pattern PROPERTY = Pattern.compile( "<td class=\"colFirst\">.*?<a href=\"([^\"]*)\">([^<]*)</a>(.*?)</td>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); }