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.array.Duplicates; 017 import com.github.xbn.array.NullContainer; 018 import com.github.xbn.array.NullElement; 019 import com.github.xbn.io.IOUtil; 020 import com.github.xbn.io.NewPrintWriterToFile; 021 import com.github.xbn.io.NewTextAppenterFor; 022 import com.github.xbn.io.PlainTextFileUtil; 023 import com.github.xbn.io.RTIOException; 024 import com.github.xbn.io.TextAppenter; 025 import com.github.xbn.keyed.SimpleNamed; 026 import com.github.xbn.lang.CrashIfObject; 027 import com.github.xbn.list.CrashIfList; 028 import com.github.xbn.text.CrashIfString; 029 import com.github.xbn.text.StringUtil; 030 import com.github.xbn.util.IfError; 031 import com.github.xbn.util.JavaRegexes; 032 import java.io.PrintWriter; 033 import java.util.ArrayList; 034 import java.util.Collections; 035 import java.util.Iterator; 036 import java.util.List; 037 import java.util.regex.Matcher; 038 import java.util.regex.Pattern; 039/** 040 <p>Represents the <a href="http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#linkpackagelist">{@code package-list}</a> for a single external Java library, including a duplicate offline file that is automatically updated from it.</p> 041 042 * @since 0.1.0 043 * @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> 044 **/ 045public class OnlineOfflineDocRoot extends SimpleNamed { 046 private final String urlDir; 047 private final String path; 048 private final List<String> pkgList; 049 /** 050 <p>Create a new instance from a url, offline path, and package list.</p> 051 052 * @param name Descriptive name of the external library. May not be {@code null} or empty, and must contain only letters, digits, and underscores. Get with {@link com.github.xbn.keyed.SimpleNamed#getName() getName}{@code ()}* 053 * @param url_toDocRoot The url directory in which the <a href="http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#linkpackagelist">{@code package-list}</a> file exists. <i>Should</i> be a valid url and end with a slash ({@code '/'}). Get with {@link #getUrlDir() getUrlDir}{@code ()}. 054 * @param offline_path The full path of the offline duplicate of the {@code package-list} file. <i>Should</i> be a valid represent a text file that is both readable and writable. Get with {@link #getPath() getPath}{@code ()} 055 * @param package_list The list of packages in the external library, as found in {@code package-list}. May not be {@code null}, empty, and its elements may not be {@code null}, empty, or duplicate. Get (a duplicate of this list) with {@link #getPackageList() getPackageList}{@code ()}. 056 * @see #newFromOnline(String, String, String, int, long, IfError, RefreshOffline, Appendable, Appendable) newFromOnline 057 * @see #newFromOffline(String, String, String, Appendable, Appendable) newFromOffline 058 */ 059 public OnlineOfflineDocRoot(String name, String url_toDocRoot, String offline_path, List<String> package_list) { 060 this(false, name, url_toDocRoot, offline_path, package_list); 061 CrashIfList.bad(package_list, "package_list", NullContainer.BAD, 1, null, NullElement.BAD, 1, null, Duplicates.BAD); 062 } 063 /** 064 <p>Avoids calling CrashIfList.bad when this OnlineOfflineDocRoot is internally created.</p> 065 */ 066 private OnlineOfflineDocRoot(boolean ignored, String name, String url_toDocRoot, String offline_path, List<String> package_list) { 067 super(name); 068 CrashIfString.nullEmpty(url_toDocRoot, "url_toDocRoot", null); 069 CrashIfString.nullEmpty(offline_path, "offline_path", null); 070 urlDir = url_toDocRoot; 071 path = offline_path; 072 try { 073 pkgList = Collections.<String>unmodifiableList(package_list); 074 } catch(RuntimeException rx) { 075 throw CrashIfObject.nullOrReturnCause(package_list, "package_list", null, rx); 076 } 077 } 078 /** 079 <p>Url of the directory in which the {@code package-list} file exists.</p> 080 081 * @return A non-{@code null} url, ending with a slash ({@code '/'}). 082 * @see #getPath() 083 */ 084 public String getUrlDir() { 085 return urlDir; 086 } 087 /** 088 <p>Full path to the offline duplicate of the {@code package-list} file.</p> 089 090 * @return A non-{@code null} path, including the file-name. 091 * @see #getUrlDir() 092 */ 093 public String getPath() { 094 return path; 095 } 096 /** 097 <p>An immutable list of all package names.</p> 098 */ 099 public List<String> getPackageList() { 100 return pkgList; 101 } 102 /** 103 <p>Overwrites or creates offline file with the current package list.</p> 104 105 <p>This overwrites the current {@linkplain #getPath() offline file} with the contents of the {@linkplain #getPackageList() package list}.</p> 106 107 * @param debug_ifNonNull If non-{@code null}, the destination for progress debugging. 108 * @param dbgError_ifNonNull If non-{@code null}, the destination for the error debugging. If {@code if_error.WARN}, this parameter may not be {@code null}. 109 * @exception RTFileNotFoundException If a {@link java.io.FileNotFoundException FileNotFoundException} is thrown when trying to open the file. The original exception (for this and all exceptions thrown by this function) is accessible with {@link java.lang.Throwable#getCause() getCause}{@code ()}. 110 * @exception RTIOException If an {@link java.io.IOException IOException} is thrown when trying to open the file. 111 * @exception SecurityException If the file is not writable. 112 * @exception RuntimeException If the file is successfully opened, but cannot be written to. 113 * @see #getPackageList() 114 */ 115 public void refreshOffline(Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) { 116 PrintWriter pw = null; 117 118 if(debug_ifNonNull != null) { 119 NewTextAppenterFor.appendable(debug_ifNonNull). 120 appentln("refreshOffline:" + toString()); 121 } 122 123 pw = new NewPrintWriterToFile().overwrite().autoFlush().build(getPath()); 124 125 for(String pkg : getPackageList()) { 126 try { 127 pw.println(pkg); 128 } catch(Exception x) { 129 throw new RuntimeException("getPath()=\"" + getPath() + "\"", x); 130 } 131 } 132 } 133 /** 134 * @return <code>{@link #appendToString(StringBuilder) appendToString}(new StringBuilder()).toString()</code> 135 */ 136 public String toString() { 137 return appendToString(new StringBuilder()).toString(); 138 } 139 /** 140 * @param to_appendTo May not be {@code null}. 141 * @see #toString() 142 */ 143 public StringBuilder appendToString(StringBuilder to_appendTo) { 144 try { 145 to_appendTo.append(getName()).append("("). 146 append(getPackageList().size()).append(" package"); 147 } catch(RuntimeException rx) { 148 throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); 149 } 150 if(getPackageList().size() > 1) { 151 to_appendTo.append("s"); 152 } 153 to_appendTo.append("): ").append(getUrlDir()); 154 155 return to_appendTo; 156 } 157 /** 158 <p>Create a new instance from an online {@code package-list}.</p> 159 160 * @param refresh_offline If {@link com.github.aliteralmind.codelet.util.RefreshOffline#YES YES}, then this ends by calling 161 <br/> <code><i>[the-new-OnlineOfflineDocRoot]</i>.{@link #refreshOffline(Appendable, Appendable) refreshOffline}(debug_ifNonNull, dbgError_ifNonNull)</code> 162 <br/>This parameter may not be {@code null}. 163 * @return <code>new {@link #OnlineOfflineDocRoot(String, String, String, List) OnlineOfflineDocRoot}(url_toDocRoot, offline_path, 164 <br/> {@link #newPackageListFromOnline(String, int, long, IfError, Appendable, Appendable) newPackageListFromOnline}(offline_path, 165 <br/> error_attemptCount, error_sleepMills, if_error, debug_ifNonNull, dbgError_ifNonNull))</code> 166 <br/>If the package list cannot be retrieved, and {@code if_error.}{@code com.github.xbn.util.IfError#WARN WARN}, this returns {@code null}. 167 <br/> 168 * @see #newFromOffline(String, String, String, Appendable, Appendable) newFromOffline 169 */ 170 public static final OnlineOfflineDocRoot newFromOnline(String name, String url_toDocRoot, String offline_path, int error_attemptCount, long error_sleepMills, IfError if_error, RefreshOffline refresh_offline, Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) throws InterruptedException { 171 List<String> pkgList = newPackageListFromOnline(url_toDocRoot, 172 error_attemptCount, error_sleepMills, if_error, debug_ifNonNull, dbgError_ifNonNull); 173 if(pkgList == null) { 174 return null; 175 } 176 OnlineOfflineDocRoot docRoot = new OnlineOfflineDocRoot(false, name, 177 url_toDocRoot, offline_path, pkgList); 178 try { 179 if(refresh_offline.isYes()) { 180 docRoot.refreshOffline(debug_ifNonNull, dbgError_ifNonNull); 181 } 182 } catch(RuntimeException rx) { 183 throw CrashIfObject.nullOrReturnCause(refresh_offline, "refresh_offline", null, rx); 184 } 185 return docRoot; 186 } 187 /** 188 <p>Create a new instance from an offline {@code package-list}.</p> 189 190 * @return <code>new {@link #OnlineOfflineDocRoot(String, String, String, List) OnlineOfflineDocRoot}(url_toDocRoot, offline_path, 191 <br/> {@link #newPackageListFromOffline(String, Appendable, Appendable) newPackageListFromOnline}(offline_path, if_error, debug_ifNonNull, dbgError_ifNonNull))</code> 192 * @see #newFromOnline(String, String, String, int, long, IfError, RefreshOffline, Appendable, Appendable) newFromOnline 193 */ 194 public static final OnlineOfflineDocRoot newFromOffline(String name, String url_toDocRoot, String offline_path, Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) { 195 return new OnlineOfflineDocRoot(false, name, url_toDocRoot, offline_path, 196 newPackageListFromOffline(offline_path, debug_ifNonNull, dbgError_ifNonNull)); 197 } 198 /** 199 <p>Create a package list from an offline {@code package-list} file.</p> 200 201 * @param pkgList_path The full path to the local package-list file. Must represent a file that is readable and writable (writability is not verified until the offline file is {@link #refreshOffline(Appendable, Appendable) refreshed}), and a valid <a href="http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#linkpackagelist">{@code package-list}</a>. 202 * @param debug_ifNonNull If non-{@code null}, the destination for progress debugging. 203 * @param dbgError_ifNonNull If non-{@code null}, the destination for the error debugging. 204 * @exception RuntimeException If opening or reading the file fails. The original exception is accessible with {@link java.lang.Throwable#getCause() getCause}{@code ()}. 205 * @see #newFromOffline(String, String, String, Appendable, Appendable) newFromOffline 206 * @see #newPackageListFromOnline(String, int, long, IfError, Appendable, Appendable) newPackageListFromOnline 207 */ 208 public static final List<String> newPackageListFromOffline(String pkgList_path, Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) { 209 if(debug_ifNonNull != null) { 210 NewTextAppenterFor.appendable(debug_ifNonNull). 211 appentln("newPackageListFromOffline:\"" + pkgList_path + "\""); 212 } 213 Iterator<String> lineItr = null; 214 try { 215 lineItr = PlainTextFileUtil.getLineIterator(pkgList_path, "pkgList_path"); 216 } catch(Exception x) { 217 String msg = "Unable to retrieve package-list from pkgList_path=\"" + pkgList_path + "\""; 218 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull). 219 appentln(msg + ": " + x); 220 221 throw new RuntimeException(msg, x); 222 } 223 try { 224 return newPkgListFromLineItr(lineItr); 225 } catch(PackageListRetrievalFailedException plfx) { 226 String msg = "pkgList_path=\"" + pkgList_path + "\""; 227 NewTextAppenterFor.appendableSuppressIfNull(dbgError_ifNonNull). 228 appentln(msg + ": " + plfx); 229 throw new PackageListRetrievalFailedException(msg, plfx); 230 } 231 } 232 /** 233 <p>Create a package list from an online {@code package-list}.</p> 234 235 * @param url_toDocRoot The url directory in which a valid <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#package-list">{@code package-list}</a> file exists. Must end with a slash ({@code '/'}). 236 * @param error_attemptCount The number of attempts to make when retrieving the {@code package-list} fails. May not be less than one. 237 * @param error_sleepMills The number of milliseconds to {@linkplain java.lang.Thread#sleep(long) sleep} between each attempt. May not be less than zero. 238 * @param if_error If {@link com.github.xbn.util.IfError#WARN WARN} then, after all attempts fail, the error is logged to {@code dbgError_ifNonNull}. If {@link com.github.xbn.util.IfError#CRASH CRASH} a {@code RuntimeException} is also thrown. This parameter may not be {@code null}. 239 * @param dbgError_ifNonNull The destination for the error-warning message. If {@code if_error.WARN}, may not be {@code null}. 240 * @return If the {@code package-list}<ul> 241 <li>Was successfully retrieved: A non-{@code null} list containing all its packages.</li> 242 <li>Could not be retrieved and {@code if_error.WARN}: {@code null}</li> 243 </ul> 244 * @exception RuntimeException If any error occurs when retrieving the {@code package-list}. The causing error is accessible with {@link java.lang.Throwable#getCause() getCause}{@code ()}. 245 * @exception RTIOException If using {@code dbgError_ifNonNull} fails. 246 * @see #newFromOnline(String, String, String, int, long, IfError, RefreshOffline, Appendable, Appendable) newFromOnline 247 * @see #newPackageListFromOffline(String, Appendable, Appendable) newPackageListFromOffline 248 */ 249 public static final List<String> newPackageListFromOnline(String url_toDocRoot, int error_attemptCount, long error_sleepMills, IfError if_error, Appendable debug_ifNonNull, Appendable dbgError_ifNonNull) throws InterruptedException { 250 TextAppenter dbgAptr = NewTextAppenterFor.appendableUnusableIfNull(debug_ifNonNull); 251 TextAppenter dbgErrAptr = null; 252 try { 253 dbgErrAptr = if_error.newAptrForApblCrashIfWarn(dbgError_ifNonNull, "dbgError_ifNonNull"); 254 } catch(RuntimeException rx) { 255 throw CrashIfObject.nullOrReturnCause(if_error, "if_error", null, rx); 256 } 257 258 if(dbgAptr.isUseable()) { 259 dbgAptr.appentln("Loading package-list from url(if_error=" + if_error + "): \"" + url_toDocRoot + "\""); 260 } 261 String text = null; 262 String errorMsg = null; 263 Exception x = null; 264 while(error_attemptCount > 0) { 265 try { 266 text = IOUtil.getWebPageSourceX(url_toDocRoot + "package-list", null); 267 268 if(text != null) { 269 return newPkgListFromLineItr(StringUtil.getLineIterator(text)); 270 } 271 } catch(Exception x2) { 272 errorMsg = "Error loading Codelet config: Failure loading package list from url: \"" + url_toDocRoot + "\""; 273 x = x2; 274 dbgErrAptr.appentln(errorMsg + ": " + x2); 275 } 276 277 error_attemptCount--; 278 279 if(error_attemptCount > 0) { 280 dbgErrAptr.appentln("newPackageListFromOnline(if_error=" + if_error + "):\"" + url_toDocRoot + "\""); 281 dbgErrAptr.appentln("Pausing " + error_sleepMills + ". Attempts remaining=" + error_attemptCount); 282 Thread.sleep(error_sleepMills); 283 } 284 } 285 286 //If if_error is null, that will have been caught by printWarning_crashIfApblNull 287 if(if_error.isCrash()) { 288 throw new PackageListRetrievalFailedException(errorMsg, x); 289 } 290 return null; 291 } 292 private static final List<String> newPkgListFromLineItr(Iterator<String> line_itr) { 293 List<String> pkgList = new ArrayList<String>(10); 294 int lineNum = 1; 295 while(line_itr.hasNext()) { 296 String pkg = line_itr.next(); 297 if(!pkgMtchr.reset(pkg).matches()) { 298 throw new IllegalArgumentException("Line " + lineNum + " is not a valid package: \"" + pkg + "\""); 299 } 300 pkgList.add(pkg); 301 lineNum++; 302 } 303 Collections.sort(pkgList); 304 return pkgList; 305 } 306 private static final Matcher pkgMtchr = Pattern.compile(JavaRegexes.PACKAGE_NAME).matcher(""); 307}