3434import java .util .ArrayList ;
3535import java .util .Base64 ;
3636import java .util .Comparator ;
37+ import java .util .HashSet ;
3738import java .util .LinkedHashMap ;
3839import java .util .List ;
3940import java .util .Locale ;
4041import java .util .Map ;
4142import java .util .Optional ;
43+ import java .util .Set ;
4244import java .util .concurrent .ConcurrentHashMap ;
4345import java .util .concurrent .atomic .AtomicBoolean ;
4446import java .util .function .Function ;
@@ -257,7 +259,7 @@ private Optional<String> lookupDocumentation(LookupKey key) {
257259 }
258260 try (Connection conn = open (dbPath .get ())) {
259261 List <TableSchema > schemas = discoverSchemas (conn );
260- boolean hasLegacySchema = hasLegacyJassdocSchema (conn );
262+ boolean hasLegacySchema = hasCompatibleLegacyJassdocSchema (conn );
261263 if (schemas .isEmpty () && !hasLegacySchema ) {
262264 WLogger .warning ("JassDoc DB found, but no compatible documentation tables were detected." );
263265 initFailed = true ;
@@ -365,7 +367,7 @@ private void triggerAsyncInit() {
365367 }
366368
367369 private @ Nullable String lookupFromLegacyJassdocTables (Connection conn , LookupKey key ) throws SQLException {
368- if (!tableExists (conn , "parameters" )) {
370+ if (!hasCompatibleLegacyJassdocSchema (conn )) {
369371 return null ;
370372 }
371373 Map <String , String > params = readKeyValueRows (conn , "parameters" , "fnname" , "param" , "value" , key .symbolName ());
@@ -778,17 +780,36 @@ void installDownloadedDatabase(Path downloaded, Path target) throws IOException
778780 try {
779781 replaceAtomically (downloaded , target );
780782 } catch (IOException installFailure ) {
781- if (!Files .exists (target ) && Files .exists (backup )) {
782- Files .copy (backup , target , StandardCopyOption .REPLACE_EXISTING ,
783- StandardCopyOption .COPY_ATTRIBUTES );
783+ if (Files .exists (backup )) {
784+ restoreBackup (backup , target , installFailure );
784785 }
785786 throw installFailure ;
786787 }
787788 }
788789
790+ void restoreBackup (Path backup , Path target , IOException installFailure ) {
791+ Path restoreTmp = null ;
792+ try {
793+ restoreTmp = Files .createTempFile (target .getParent (), "jassdoc-restore-" , ".tmp" );
794+ Files .copy (backup , restoreTmp , StandardCopyOption .REPLACE_EXISTING ,
795+ StandardCopyOption .COPY_ATTRIBUTES );
796+ replaceAtomically (restoreTmp , target );
797+ } catch (IOException restoreFailure ) {
798+ installFailure .addSuppressed (restoreFailure );
799+ } finally {
800+ if (restoreTmp != null ) {
801+ try {
802+ Files .deleteIfExists (restoreTmp );
803+ } catch (IOException cleanupFailure ) {
804+ installFailure .addSuppressed (cleanupFailure );
805+ }
806+ }
807+ }
808+ }
809+
789810 private void validateDownloadedDatabase (Path downloaded ) throws IOException {
790811 try (Connection conn = open (downloaded )) {
791- if (discoverSchemas (conn ).isEmpty () && !hasLegacyJassdocSchema (conn )) {
812+ if (discoverSchemas (conn ).isEmpty () && !hasCompatibleLegacyJassdocSchema (conn )) {
792813 throw new IOException ("Downloaded file is not a compatible JassDoc database" );
793814 }
794815 } catch (SQLException e ) {
@@ -818,7 +839,19 @@ private HttpURLConnection openHttpConnection(URL url) throws IOException {
818839 return (HttpURLConnection ) url .openConnection ();
819840 }
820841
821- String value = proxySetting .get ();
842+ URI proxyUri = parseHttpProxyUri (proxySetting .get ());
843+ int port = proxyUri .getPort () >= 0 ? proxyUri .getPort () : 80 ;
844+ Proxy proxy = new Proxy (Proxy .Type .HTTP , new InetSocketAddress (proxyUri .getHost (), port ));
845+ HttpURLConnection connection = (HttpURLConnection ) url .openConnection (proxy );
846+ if (proxyUri .getUserInfo () != null ) {
847+ String credentials = Base64 .getEncoder ().encodeToString (
848+ proxyUri .getUserInfo ().getBytes (StandardCharsets .UTF_8 ));
849+ connection .setRequestProperty ("Proxy-Authorization" , "Basic " + credentials );
850+ }
851+ return connection ;
852+ }
853+
854+ static URI parseHttpProxyUri (String value ) throws IOException {
822855 URI proxyUri ;
823856 try {
824857 proxyUri = URI .create (value .contains ("://" ) ? value : "http://" + value );
@@ -828,15 +861,11 @@ private HttpURLConnection openHttpConnection(URL url) throws IOException {
828861 if (proxyUri .getHost () == null ) {
829862 throw new IOException ("Invalid JassDoc proxy URL: missing host" );
830863 }
831- int port = proxyUri .getPort () >= 0 ? proxyUri .getPort () : 80 ;
832- Proxy proxy = new Proxy (Proxy .Type .HTTP , new InetSocketAddress (proxyUri .getHost (), port ));
833- HttpURLConnection connection = (HttpURLConnection ) url .openConnection (proxy );
834- if (proxyUri .getUserInfo () != null ) {
835- String credentials = Base64 .getEncoder ().encodeToString (
836- proxyUri .getUserInfo ().getBytes (StandardCharsets .UTF_8 ));
837- connection .setRequestProperty ("Proxy-Authorization" , "Basic " + credentials );
864+ if (!"http" .equalsIgnoreCase (proxyUri .getScheme ())) {
865+ throw new IOException ("Unsupported JassDoc proxy scheme '" + proxyUri .getScheme ()
866+ + "'; use an http:// proxy URL" );
838867 }
839- return connection ;
868+ return proxyUri ;
840869 }
841870
842871 static boolean shouldBypassProxy (String host , String noProxySetting ) {
@@ -913,8 +942,32 @@ private List<TableSchema> discoverSchemas(Connection conn) throws SQLException {
913942 return result ;
914943 }
915944
916- private boolean hasLegacyJassdocSchema (Connection conn ) throws SQLException {
917- return tableExists (conn , "parameters" );
945+ private boolean hasCompatibleLegacyJassdocSchema (Connection conn ) throws SQLException {
946+ return tableHasColumns (conn , "parameters" , "fnname" , "param" , "value" )
947+ && (!tableExists (conn , "annotations" )
948+ || tableHasColumns (conn , "annotations" , "fnname" , "anname" , "value" ))
949+ && (!tableExists (conn , "params_extra" )
950+ || tableHasColumns (conn , "params_extra" , "fnname" , "param" , "anname" , "value" ));
951+ }
952+
953+ private boolean tableHasColumns (Connection conn , String tableName , String ... requiredColumns )
954+ throws SQLException {
955+ Set <String > columns = new HashSet <>();
956+ DatabaseMetaData md = conn .getMetaData ();
957+ try (ResultSet result = md .getColumns (null , null , tableName , "%" )) {
958+ while (result .next ()) {
959+ String name = result .getString ("COLUMN_NAME" );
960+ if (name != null ) {
961+ columns .add (name .toLowerCase (Locale .ROOT ));
962+ }
963+ }
964+ }
965+ for (String required : requiredColumns ) {
966+ if (!columns .contains (required )) {
967+ return false ;
968+ }
969+ }
970+ return true ;
918971 }
919972
920973 private boolean tableExists (Connection conn , String tableName ) throws SQLException {
0 commit comments