blob: 58a3e6ac4c6ee4ed870865556252bdcf9cadee14 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript2.requirejs;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.modules.Places;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
/**
*
* @author Petr Pisl
*/
@NbBundle.Messages({
"doc.building=Loading RequireJS Documentation",
"# {0} - the documentation URL",
"doc.cannotGet=Cannot load RequireJS documentation from \"{0}\".",
"doc.notFound=Documentation not found."
})
public class RequireJsDataProvider {
private static final Logger LOG = Logger.getLogger(RequireJsDataProvider.class.getSimpleName()); //NOI18N
private static RequestProcessor RP = new RequestProcessor(RequireJsDataProvider.class);
private static RequireJsDataProvider INSTANCE;
private boolean loadingStarted;
private ProgressHandle progress;
private static final String CACHE_FOLDER_NAME = "requirejs-doc"; //NOI18N
private static final String API_FILE = "api.html"; //NOI18N
public static final String API_URL = "http://requirejs.org/docs/api.html";
private static final int URL_CONNECTION_TIMEOUT = 1000; //ms
private static final int URL_READ_TIMEOUT = URL_CONNECTION_TIMEOUT * 3; //ms
private static final String SEARCH_TEXT = "<p id=\"config-"; //NOI18N
/**
* Translating names from documentation to the real option names
*/
private static final HashMap<String, String> TRANSLATE_NAME = new HashMap<>();
static {
TRANSLATE_NAME.put("moduleconfig", "config");//NOI18N
// remove this option
TRANSLATE_NAME.put("map-notes", ""); //NOI18N
}
private RequireJsDataProvider() {
loadingStarted = false;
}
public static synchronized RequireJsDataProvider getDefault() {
if (INSTANCE == null) {
INSTANCE = new RequireJsDataProvider();
}
return INSTANCE;
}
public Collection<String> getConfigurationOptions() {
Collection<String> options = getConfigurationOptionsFromDoc();
if (options.isEmpty()) {
options = getConfigurationOptionsStatic();
}
return options;
}
public String getDocForHtmlTagAttribute(final String attributeName) {
String api = getContentApiFile();
if (api != null) {
String searchText = "href=\"#" + attributeName + "\" name=\"" + attributeName + "\""; //NOI18N
int start = api.indexOf(searchText);
if (start > 0) {
String tmpText = api.substring(0, start);
start = tmpText.lastIndexOf("<h3>"); // NOI18N
int end = api.indexOf("<h3>", start + 10); //NOI18N
if (start < end) {
return api.substring(start, end);
}
}
}
return "";
}
public String getDocFocOption(String name) {
String api = getContentApiFile();
if (api != null) {
int index = api.indexOf(SEARCH_TEXT + name);
if (index == -1 && TRANSLATE_NAME.containsValue(name)) {
for (Map.Entry<String, String> entry : TRANSLATE_NAME.entrySet()) {
if (entry.getValue().equals(name)) {
index = api.indexOf(SEARCH_TEXT + entry.getKey());
break;
}
}
}
if (index > 0) {
int start = index;
index = api.indexOf(SEARCH_TEXT, start + SEARCH_TEXT.length());
if (index == -1) {
index = api.indexOf("</div>", start + SEARCH_TEXT.length());
}
if (index > 0) {
return api.substring(start, index);
}
}
}
return Bundle.doc_notFound();
}
private void startLoading() {
LOG.fine("start loading doc"); //NOI18N
progress = ProgressHandle.createHandle(Bundle.doc_building());
progress.start(1);
}
private Collection<String> getConfigurationOptionsFromDoc() {
List<String> result = new ArrayList<>();
String api = getContentApiFile();
if (api != null) {
int index = api.indexOf(SEARCH_TEXT);
while (index > 0) {
int start = index + SEARCH_TEXT.length();
index = api.indexOf('"', start);
String option = api.substring(start, index);
if (TRANSLATE_NAME.containsKey(option)) {
option = TRANSLATE_NAME.get(option);
}
if (!(option == null || option.isEmpty())) {
result.add(option);
}
index = api.indexOf(SEARCH_TEXT, index);
}
}
return result;
}
public static File getCachedAPIFile() {
String pathFile = new StringBuilder().append(CACHE_FOLDER_NAME).append('/').append(API_FILE).toString();
File cacheFile = Places.getCacheSubfile(pathFile);
return cacheFile;
}
private String getContentApiFile() {
String result = null;
try {
File cacheFile = getCachedAPIFile();
if (!cacheFile.exists()) {
//if any of the files is not loaded yet, start the loading process
if (!loadingStarted) {
loadingStarted = true;
startLoading();
}
//load from web and cache locally
loadDoc(cacheFile);
if (progress != null) {
progress.progress(1);
progress.finish();
progress = null;
}
LOG.log(Level.FINE, "Loading doc finished."); //NOI18N
}
result = getFileContent(cacheFile);
} catch (URISyntaxException | IOException ex) {
loadingStarted = false;
if (progress != null) {
progress.finish();
progress = null;
}
LOG.log(Level.INFO, "Cannot load RequireJS documentation from \"{0}\".", new Object[]{API_URL}); //NOI18N
LOG.log(Level.INFO, "", ex);
}
return result;
}
private Collection<String> getConfigurationOptionsStatic() {
List<String> known = new ArrayList<>();
for (ConfigOption option : ConfigOption.values()) {
known.add(option.getName());
}
return known;
}
private void loadDoc(File cacheFile) throws URISyntaxException, MalformedURLException, IOException {
LOG.fine("start loading doc"); //NOI18N
URL url = new URI(API_URL).toURL();
synchronized (cacheFile) {
String tmpFileName = cacheFile.getAbsolutePath() + ".tmp";
File tmpFile = new File(tmpFileName);
try (Writer writer = new OutputStreamWriter(new FileOutputStream(tmpFile), StandardCharsets.UTF_8)) {
loadURL(url, writer, StandardCharsets.UTF_8);
writer.close();
boolean success = tmpFile.renameTo(cacheFile);
if (!success) {
LOG.log(Level.WARNING, "Renaming {0} to {1] was not successful.", new Object[]{tmpFile.getAbsolutePath(), cacheFile.getAbsolutePath()});
}
} finally {
if (tmpFile.exists()) {
boolean success = tmpFile.delete();
if (!success) {
LOG.log(Level.WARNING, "Deleting {0} faild.", new Object[]{tmpFile.getAbsolutePath()});
}
}
}
}
}
private void loadURL(URL url, Writer writer, Charset charset) throws IOException {
if (charset == null) {
charset = Charset.defaultCharset();
}
URLConnection con = url.openConnection();
con.setConnectTimeout(URL_CONNECTION_TIMEOUT);
con.setReadTimeout(URL_READ_TIMEOUT);
con.connect();
Reader r = new InputStreamReader(new BufferedInputStream(con.getInputStream()), charset);
char[] buf = new char[2048];
int read;
while ((read = r.read(buf)) != -1) {
writer.write(buf, 0, read);
}
r.close();
}
private String getFileContent(File file) throws IOException {
Reader r = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
StringBuilder sb = new StringBuilder();
try {
char[] buf = new char[2048];
int read;
while ((read = r.read(buf)) != -1) {
sb.append(buf, 0, read);
}
} finally {
r.close();
}
return sb.toString();
}
}