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/> &nbsp; &nbsp; <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/> &nbsp; &nbsp; <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}