001/*license*\ 002 Codelet: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) 003 004 This software is dual-licensed under the: 005 - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; 006 - Apache Software License (ASL) version 2.0. 007 008 Either license may be applied at your discretion. More information may be found at 009 - http://en.wikipedia.org/wiki/Multi-licensing. 010 011 The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: 012 - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt 013 - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt 014\*license*/ 015package com.github.aliteralmind.codelet.util; 016 import com.github.xbn.io.NewTextAppenterFor; 017 import com.github.xbn.lang.BadDuplicateException; 018 import com.github.xbn.lang.CrashIfObject; 019 import com.github.xbn.util.IfError; 020 import java.util.Collections; 021 import java.util.Iterator; 022 import java.util.Map; 023 import java.util.TreeMap; 024 import java.util.regex.Pattern; 025 import static com.github.xbn.lang.XbnConstants.*; 026/** 027 <p>Collection of {@code package-list}s from all external Java libraries used by a project, for mapping a package name to its JavaDoc document root url--even if that online {@code package-list} is inaccessible. This information is the equivalent of {@code javadoc.exe}'s <a href="http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#link">{@code -link}</a> and {@code -linkoffline} options.</p> 028 029 <p><i>While it may be possible to read in the values of {@code -link} and {@code -linkoffline}, as passed into {@code javadoc.exe}, doing so would make Codelet more dependant on {@code com.sun.javadoc}, which is against its goal of <a href="http://stackoverflow.com/questions/23138806/how-to-make-inline-taglets-which-require-com-sun-more-cross-platform-is-there">minimizing dependencies</a> on this non-standard package.</i></p> 030 031 * @since 0.1.0 032 * @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://codelet.aliteralmind.com">{@code http://codelet.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/codelet">{@code https://github.com/aliteralmind/codelet}</a> 033 **/ 034public class AllOnlineOfflineDocRoots { 035 private final Map<String,OnlineOfflineDocRoot> nameToRootMap; 036 private final Map<String,OnlineOfflineDocRoot> urlToRootMap ; 037 private final Map<String,String> pkgToUrlMap ; 038 /** 039 <p>An immutable map whose key is the doc-root's {@linkplain OnlineOfflineDocRoot#getName() name}, and value is its {@code OnlineOfflineDocRoot}.</p> 040 041 * @see #getPkgToUrlMap() 042 */ 043 public Map<String,OnlineOfflineDocRoot> getNameToRootMap() { 044 return nameToRootMap; 045 } 046 /** 047 <p>An immutable map whose key is the doc-root's {@linkplain OnlineOfflineDocRoot#getUrlDir() url}, and value is its {@code OnlineOfflineDocRoot}.</p> 048 049 * @see #getPkgToUrlMap() 050 */ 051 public Map<String,OnlineOfflineDocRoot> getUrlToRootMap() { 052 return urlToRootMap; 053 } 054 /** 055 <p>An immutable map whose key is a {@linkplain OnlineOfflineDocRoot#getPackageList() package}, and value is its document root url.</p> 056 057 * @see #getUrlToRootMap() 058 * @see #getNameToRootMap() 059 */ 060 public Map<String,String> getPkgToUrlMap() { 061 return pkgToUrlMap; 062 } 063 /** 064 * @return <code>{@link #appendToString(StringBuilder) appendToString}(new StringBuilder()).toString()</code> 065 */ 066 public String toString() { 067 return appendToString(new StringBuilder()).toString(); 068 } 069 /** 070 * @param to_appendTo May not be {@code null}. 071 * @see #toString() 072 */ 073 public StringBuilder appendToString(StringBuilder to_appendTo) { 074 try { 075 to_appendTo.append("All doc-roots:").append(LINE_SEP); 076 } catch(RuntimeException rx) { 077 throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); 078 } 079 080 for (Map.Entry<String,OnlineOfflineDocRoot> entry : getNameToRootMap().entrySet()) { 081 to_appendTo.append(" - ").append(entry.getValue()).append(LINE_SEP); 082 } 083 084 return to_appendTo; 085 } 086 /** 087 <p>Create a new {@code AllOnlineOfflineDocRoots} from a configuration file, where each line contains two items: the doc-root's offline file path and online document-root url.</p> 088 089 <p>Each line in a configuration file is in the format</p> 090 091<blockquote><pre>[offline_file_path] [url]</pre></blockquote> 092 093 <p>Where<ul> 094 <li><code>[offline_file_path]</code> is the full path of the locally-stored file. This must exist, and be both readable and writable.</li> 095 <li><code>[url]</code> is the url to the JavaDoc document root ({@code "{@docRoot}"}) for an external Java library. Must end with a slash ({@code '/'}), and must contain the library's {@code "package-list"} file.</li> 096 </ul>The file-path and url are separated with at least one space or tab.</p> 097 098 <p>(Lines starting with {@code '#'} are ignored. Empty lines are not allowed.)</p> 099 100 <h4>Example</h4> 101 102 <p>If both {@code offlineName_prefixPath} and {@code offlineName_postfix} are {@code null}:</p> 103 104<blockquote>{@code C:\java_code\config\javadoc_offline_package_lists\java.txt http://docs.oracle.com/javase/7/docs/api/}</blockquote> 105 106 <p>An equivalent is to set<ul> 107 <li>{@code offlineName_prefixPath} to {@code "C:\java_code\config\javadoc_offline_package_lists\"}</li> 108 <li>and {@code offlineName_postfix} to {@code ".txt"}</li> 109 </ul>and then the config line can be</p> 110 111<blockquote>{@code java http://docs.oracle.com/javase/7/docs/api/}</blockquote> 112 113 <p>Steps for each line in {@code line_itr}:<ol> 114 <li>If {@code online_attemptCount} is<ul> 115 <li>Greater than zero: This creates the {@code OnlineOfflineDocRoot} with 116 <br/> <code>{@link OnlineOfflineDocRoot}.{@link OnlineOfflineDocRoot#newFromOnline(String, String, String, int, long, IfError, RefreshOffline, Appendable, Appendable) newFromOnline}(name, url, path, online_attemptCount, online_sleepMills, if_error, refresh_offline, debug_ifNonNull, dbgError_ifNonNull)</code></li> 117 <li>Equal to zero (<i>or if retrieving from online fails, and {@code if_error.}{@link com.github.xbn.util.IfError#WARN WARN}</i>): This creates the {@code OnlineOfflineDocRoot} with 118 <br/> <code>OnlineOfflineDocRoot.{@link OnlineOfflineDocRoot#newFromOffline(String, String, String, Appendable, Appendable) newFromOffline}(name, url, path, debug_ifNonNull, dbgError_ifNonNull)</code></li> 119 </ul></li> 120 </ol></p> 121 122 * @param line_itr May not be {@code null}, and <i>should</i> have at least one item. All offline paths must be unique, and all urls must be unique. 123 * @param offlineName_prefixPath If non-{@code null}, this is the base directory appended to each offline name, as described above. Setting this to {@code null} is the same as the empty string ({@code ""}). 124 * @param offlineName_postfix If non-{@code null}, this is the postfix appended to each offline name. 125 * @param refresh_offline When {@code online_attemptCount} is greater than zero, should the offline {@code package-list} be refreshed from the online version? If {@code online_attemptCount} is zero, this parameter is ignored. 126 * @see com.github.aliteralmind.codelet.CodeletBootstrap#EXTERNAL_DOC_ROOT_URL_FILE 127 */ 128 public static final AllOnlineOfflineDocRoots newFromConfigLineIterator(Iterator<String> line_itr, String offlineName_prefixPath, String offlineName_postfix, int online_attemptCount, long online_sleepMills, RefreshOffline refresh_offline, IfError if_error, Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) throws InterruptedException { 129 130 Map<String,OnlineOfflineDocRoot> nameToRootMap = new TreeMap<String,OnlineOfflineDocRoot>(); 131 Map<String,OnlineOfflineDocRoot> urlToRootMap = new TreeMap<String,OnlineOfflineDocRoot>(); 132 Map<String,String> pkgToUrlMap = new TreeMap<String,String>(); 133 134 int lineNum = 1; 135 try { 136 while(line_itr.hasNext()) { 137 String line = line_itr.next(); 138 if(line.startsWith("#")) { 139 continue; 140 } 141 String[] nameUrl = oneOrMoreSpcTabPtrn.split(line, 0); 142 if(nameUrl.length != 2) { 143 String msg = "[line=" + lineNum + "] Line has " + nameUrl.length + " parts. Must have two: \"" + line + "\""; 144 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull). 145 appentln(msg); 146 throw new IllegalArgumentException(msg); 147 } 148 String name = nameUrl[0]; 149 String url = nameUrl[1]; 150 String path = offlineName_prefixPath + nameUrl[0] + offlineName_postfix; 151 152 OnlineOfflineDocRoot docRoot = null; 153 154 if(online_attemptCount > 0) { 155 docRoot = OnlineOfflineDocRoot.newFromOnline(name, url, path, online_attemptCount, online_sleepMills, if_error, refresh_offline, debug_ifNonNull, dbgError_ifNonNull); 156 } 157 158 if(docRoot == null) { 159 //Either retrieving online failed, or online_attemptCount is zero 160 NewTextAppenterFor.appendableSuppressIfNull(debug_ifNonNull).appentln("Online package-list not retrieved. Retrieving offline version."); 161 docRoot = OnlineOfflineDocRoot.newFromOffline(name, url, path, debug_ifNonNull, dbgError_ifNonNull); 162 } 163 164 if(nameToRootMap.containsKey(name)) { 165 String msg = "[line=" + lineNum + "] Duplicate name: \"" + line + "\""; 166 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull).appentln(msg); 167 throw new BadDuplicateException(msg); 168 } 169 170 if(urlToRootMap.containsKey(url)) { 171 String msg = "[line=" + lineNum + "] Duplicate url: \"" + url + "\""; 172 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull).appentln(msg); 173 throw new BadDuplicateException(msg); 174 } 175 176 for(String pkg : docRoot.getPackageList()) { 177 if(pkgToUrlMap.containsKey(pkg)) { 178 String msg = "[line=" + lineNum + "] Duplicate package: \"" + pkg + "\". In both " + docRoot.getName() + " and " + urlToRootMap.get(pkgToUrlMap.get(pkg)).getName() + "."; 179 if(dbgError_ifNonNull != null) { 180 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull).appentln(msg); 181 } 182 throw new BadDuplicateException(msg); 183 } 184 pkgToUrlMap.put(pkg, url); 185 pkgToUrlMap.put(pkg, docRoot.getUrlDir()); 186 } 187 188 nameToRootMap.put(name, docRoot); 189 urlToRootMap.put(url, docRoot); 190 191 lineNum++; 192 } 193 } catch(RuntimeException rx) { 194 throw CrashIfObject.nullOrReturnCause(line_itr, "line_itr", null, rx); 195 } 196 return new AllOnlineOfflineDocRoots( 197 Collections.unmodifiableMap(nameToRootMap), 198 Collections.unmodifiableMap(urlToRootMap), 199 Collections.unmodifiableMap(pkgToUrlMap)); 200 } 201 private static final Pattern oneOrMoreSpcTabPtrn = Pattern.compile("[ \t]+"); 202 private AllOnlineOfflineDocRoots(Map<String,OnlineOfflineDocRoot> nameToRoot_map, Map<String,OnlineOfflineDocRoot> urlToRoot_map, Map<String,String> pkgToUrl_map) { 203 nameToRootMap = nameToRoot_map; 204 urlToRootMap = urlToRoot_map; 205 pkgToUrlMap = pkgToUrl_map; 206 } 207}