content: /*
   * 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 accord.api;
  
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import accord.primitives.Txn;
  import accord.primitives.TxnId;
  import accord.primitives.TxnId.FastPath;
  import accord.primitives.TxnId.FastPaths;
  import accord.primitives.TxnId.MediumPath;
  import accord.utils.Invariants;
  import accord.utils.TinyEnumSet;
  
  import static accord.api.ProtocolModifiers.QuorumEpochIntersections.ChaseFixedPoint.Chase;
  import static accord.api.ProtocolModifiers.QuorumEpochIntersections.ChaseFixedPoint.DoNotChase;
  import static accord.api.ProtocolModifiers.QuorumEpochIntersections.Include.Owned;
  import static accord.api.ProtocolModifiers.QuorumEpochIntersections.Include.Unsynced;
  import static accord.api.ProtocolModifiers.Toggles.DependencyElision.IF_DURABLY_COMMITTED;
  import static accord.api.ProtocolModifiers.Toggles.InformOfDurability.ALL;
  import static accord.api.ProtocolModifiers.Toggles.SendStableMessages.FOR_READS;
  import static accord.api.ProtocolModifiers.Toggles.SendStableMessages.FOR_READS_OR_NONE_IF_FASTEXEC;
  import static accord.primitives.Txn.Kind.EphemeralRead;
  import static accord.primitives.Txn.Kind.VisibilitySyncPoint;
  
  /**
   * Configure various protocol behaviours. Many of these switches are correctness impacting, and should not be touched.
   */
  public class ProtocolModifiers
  {
      public static class QuorumEpochIntersections
      {
          public enum ChaseFixedPoint
          {
              Chase, DoNotChase;
          }
  
          public enum Include
          {
              // include all nodes and epochs with unsynced ranges
              Unsynced,
              // include only replicas that own the ranges, and only the ownership epochs
              Owned;
  
              private static Include max(Include a, Include b)
              {
                  return a.compareTo(b) >= 0 ? a : b;
              }
          }
  
          public static class ChaseAndInclude
          {
              public final ChaseFixedPoint chase;
              public final Include include;
  
              public ChaseAndInclude(ChaseFixedPoint chase, Include include)
              {
                  this.chase = chase;
                  this.include = include;
              }
          }
  
          static class Spec
          {
              private static final Pattern PARSE = Pattern.compile("(preaccept|accept|commit|stable|recover)=([+-]{0,2})");
  
              final ChaseAndInclude preaccept;
              final Include accept, commit, stable, recover;
              final Include preacceptOrRecover, preacceptOrCommit;
  
              Spec(ChaseAndInclude preaccept, Include accept, Include commit, Include stable, Include recover)
              {
                  this.preaccept = preaccept;
                  Invariants.require(preaccept.chase == DoNotChase, "PreAccept chasing epoch is not implemented as not needed for current formulation and it complicated some aspects. An implementation can be found in git history.");
                  this.accept = accept;
                  this.commit = commit;
                  this.stable = stable;
                  this.recover = recover;
                  this.preacceptOrRecover = Include.max(preaccept.include, recover);
                  this.preacceptOrCommit = Include.max(preaccept.include, commit);
              }
  
              static Spec parse(String description)
              {
                  Matcher m = PARSE.matcher(description);
                  ChaseAndInclude preaccept = null;
                  Include accept = null;
                  Include commit = null;
                  Include stable = null;
                  Include recover = null;
                  while (m.find())
                  {
                      ChaseFixedPoint cfp = DoNotChase;
                      Include include = Owned;
                      String str = m.group(2);
                      for (int i = 0 ; i < str.length() ; ++i)
                      {
                          switch (str.charAt(i))
                          {
                              default: throw new AssertionError("Unexpected char: '" + str.charAt(i) + "'");
                              case '+': cfp = Chase; break;
                              case '-': include = Unsynced; break;
                          }
                      }
  
                      switch (m.group(1))
                      {
                          default: throw new AssertionError("Unexpected phase: " + m.group(1));
                          case "preaccept": preaccept = new ChaseAndInclude(cfp, include); break;
                          case "accept": accept = include; Invariants.require(cfp == DoNotChase, "Invalid to specify ChaseFixedPoint.Chase for accept"); break;
                          case "commit": commit = include; Invariants.require(cfp == DoNotChase, "Invalid to specify ChaseFixedPoint.Chase for commit"); break;
                          case "stable": stable = include; Invariants.require(cfp == DoNotChase, "Invalid to specify ChaseFixedPoint.Chase for stable"); break;
                          case "recover": recover = include; Invariants.require(cfp == DoNotChase, "Invalid to specify ChaseFixedPoint.Chase for stable"); break;
                      }
                  }
  
                  Invariants.require(preaccept != null, "preaccept not specified for quorum epoch intersections: " + description);
                  Invariants.require(accept != null, "accept not specified for quorum epoch intersections: " + description);
                  Invariants.require(commit != null, "commit not specified for quorum epoch intersections: " + description);
                  Invariants.require(stable != null, "stable not specified for quorum epoch intersections: " + description);
                  Invariants.require(recover != null, "recover not specified for quorum epoch intersections: " + description);
                  return new Spec(preaccept, accept, commit, stable, recover);
              }
          }
  
          static
          {
              Spec spec = Spec.parse(System.getProperty("accord.quorums.epochs", "preaccept=-,accept=-,commit=-,stable=-,recover=-"));
              preaccept = spec.preaccept;
              accept = spec.accept;
              commit = spec.commit;
              stable = spec.stable;
              recover = spec.recover;
              preacceptOrRecover = spec.preacceptOrRecover;
              preacceptOrCommit = spec.preacceptOrCommit;
          }
  
          // TODO (desired): support fixed-point chasing for recovery
          public static final ChaseAndInclude preaccept;
          public static final Include accept, commit, stable, recover;
          public static final Include preacceptOrRecover, preacceptOrCommit;
      }
  
      public static class Faults
      {
          static class Spec
          {
              // note that {txn,syncPoint}DiscardPreAcceptDeps faults expect filterDuplicateDependenciesFromAcceptReply to be off
              final boolean txnInstability, txnDiscardPreAcceptDeps;
              final boolean syncPointInstability, syncPointDiscardPreAcceptDeps;
  
              Spec(boolean txnInstability, boolean txnDiscardPreAcceptDeps, boolean syncPointInstability, boolean syncPointDiscardPreAcceptDeps)
              {
                  this.txnInstability = txnInstability;
                  this.txnDiscardPreAcceptDeps = txnDiscardPreAcceptDeps;
                  this.syncPointInstability = syncPointInstability;
                  this.syncPointDiscardPreAcceptDeps = syncPointDiscardPreAcceptDeps;
              }
          }
  
          static
          {
              Spec spec = new Spec(false, false, false, false);
              txnInstability = spec.txnInstability;
              txnDiscardPreAcceptDeps = spec.txnDiscardPreAcceptDeps;
              syncPointInstability = spec.syncPointInstability;
              syncPointDiscardPreAcceptDeps = spec.syncPointDiscardPreAcceptDeps;
          }
  
          public static final boolean txnInstability, txnDiscardPreAcceptDeps;
          public static final boolean syncPointInstability, syncPointDiscardPreAcceptDeps;
  
          public static boolean discardPreAcceptDeps(TxnId txnId)
          {
              return (txnDiscardPreAcceptDeps | syncPointDiscardPreAcceptDeps)
                     && (txnId.isSyncPoint() ? syncPointDiscardPreAcceptDeps : txnDiscardPreAcceptDeps);
          }
      }
  
      public static class Toggles
      {
          private static FastPaths permittedFastPaths = new FastPaths(FastPath.values());
          public static boolean usePrivilegedCoordinator() { return permittedFastPaths.hasPrivilegedCoordinator(); }
          public static void setPermittedFastPaths(FastPaths newPermittedFastPaths) { permittedFastPaths = newPermittedFastPaths; }
          public static FastPath ensurePermitted(FastPath path) { return path.toPermitted(permittedFastPaths); }
  
          private static MediumPath defaultMediumPath = MediumPath.TrackStable;
          public static MediumPath defaultMediumPath() { return defaultMediumPath; }
          public static void setDefaultMediumPath(MediumPath newDefaultMediumPath) { defaultMediumPath = newDefaultMediumPath; }
  
          private static boolean filterDuplicateDependenciesFromAcceptReply = true;
          public static boolean filterDuplicateDependenciesFromAcceptReply() { return filterDuplicateDependenciesFromAcceptReply; }
          public static void setFilterDuplicateDependenciesFromAcceptReply(boolean newFilterDuplicateDependenciesFromAcceptReply) { filterDuplicateDependenciesFromAcceptReply = newFilterDuplicateDependenciesFromAcceptReply; }
  
          public enum SendStableMessages { TO_ALL, FOR_READS, FOR_READS_OR_NONE_IF_FASTEXEC}
          private static SendStableMessages sendStableMessages = FOR_READS_OR_NONE_IF_FASTEXEC;
          public static void setSendStableMessages(SendStableMessages newSendStableMessages) { sendStableMessages = newSendStableMessages; }
          public static boolean sendOnlyReadStableMessages() { return sendStableMessages.compareTo(FOR_READS) >= 0; }
          public static boolean sendNoStableIfFastExec() { return sendStableMessages == FOR_READS_OR_NONE_IF_FASTEXEC; }
  
          private static boolean permitLocalExecution = true;
          public static boolean permitLocalExecution() { return permitLocalExecution; }
          public static void setPermitLocalExecution(boolean newPermitLocalExecution) { permitLocalExecution = newPermitLocalExecution; }
  
          private static boolean requiresUniqueHlcs = true;
          public static boolean requiresUniqueHlcs() { return requiresUniqueHlcs; }
          public static void setRequiresUniqueHlcs(boolean newRequiresUniqueHlcs) { requiresUniqueHlcs = newRequiresUniqueHlcs; }
  
          private static int markStaleIfCannotExecute = TinyEnumSet.encode(Txn.Kind.Write);
          public static boolean markStaleIfCannotExecute(TxnId txnId) { return TinyEnumSet.contains(markStaleIfCannotExecute, txnId.kindOrdinal()); }
          public static boolean markStaleIfCannotExecute(Txn.Kind kind) { return TinyEnumSet.contains(markStaleIfCannotExecute, kind); }
          public static void setMarkStaleIfCannotExecute(Txn.Kind ... kinds)
          {
              int newMarkStaleIfCannotExecute = TinyEnumSet.encode(kinds);
              Invariants.require(TinyEnumSet.contains(newMarkStaleIfCannotExecute, Txn.Kind.Write));
              markStaleIfCannotExecute = newMarkStaleIfCannotExecute;
          }
  
          private static int transitiveDependenciesAreVisible = TinyEnumSet.encode(VisibilitySyncPoint);
          public static boolean isTransitiveDependencyVisible(TxnId txnId) { return TinyEnumSet.contains(transitiveDependenciesAreVisible, txnId.kindOrdinal()); }
          public static boolean isTransitiveDependencyVisible(Txn.Kind kind) { return TinyEnumSet.contains(transitiveDependenciesAreVisible, kind); }
          public static void setTransitiveDependenciesAreVisible(Txn.Kind ... kinds)
          {
              int newTransitiveDependenciesAreVisible = TinyEnumSet.encode(kinds);
              Invariants.require(TinyEnumSet.contains(newTransitiveDependenciesAreVisible, Txn.Kind.VisibilitySyncPoint));
              transitiveDependenciesAreVisible = newTransitiveDependenciesAreVisible;
          }
  
          public enum DependencyElision { OFF, ON, IF_DURABLY_COMMITTED, IF_DURABLY_PREAPPLIED }
          private static DependencyElision dependencyElision = IF_DURABLY_COMMITTED;
          public static DependencyElision dependencyElision() { return dependencyElision; }
          public static void setDependencyElision(DependencyElision newDependencyElision) { dependencyElision = newDependencyElision; }
  
          private static boolean visitMaybePruned = true;
          public static boolean shouldVisitMaybePruned() { return visitMaybePruned; }
          public static void setVisitMaybePruned(boolean newVisitMaybePruned) { visitMaybePruned = newVisitMaybePruned; }
  
          public enum InformOfDurability { HOME, ALL }
          private static InformOfDurability informOfDurability = ALL;
          public static InformOfDurability informOfDurability() { return informOfDurability; }
          public static void setInformOfDurability(InformOfDurability newInformOfDurability) { informOfDurability = newInformOfDurability; }
  
          public enum FastExec { DISABLED, MAY_BYPASS_COMMANDSFORKEY, MAY_BYPASS_SAFESTORE }
          private static FastExec fastReadExec = FastExec.MAY_BYPASS_SAFESTORE;
          public static boolean fastReadsMayBypassSafeStore(TxnId txnId) { return fastReadExec == FastExec.MAY_BYPASS_SAFESTORE && (dataStoreDetectsFutureReads() || txnId.is(EphemeralRead)); }
          public static boolean fastReadsMayBypassCommandsForKey(TxnId txnId) { return fastReadExec != FastExec.DISABLED && !txnId.is(Txn.Kind.Write); }
          public static void setFastReadExec(FastExec newFastReads) { fastReadExec = newFastReads; }
  
          private static boolean fastReadExecMayResendTxn = true;
          public static boolean fastReadExecMayResendTxn() { return fastReadExecMayResendTxn; }
          public static void setFastReadExecMayResendTxn(boolean newFastReadExecMayResendTxn) { fastReadExecMayResendTxn = newFastReadExecMayResendTxn; }
  
          private static FastExec fastWriteExec = FastExec.MAY_BYPASS_SAFESTORE;
          public static boolean fastWritesMayBypassSafeStore() { return fastWriteExec == FastExec.MAY_BYPASS_SAFESTORE; }
          public static boolean fastWritesMayBypassCommandsForKey() { return fastWriteExec != FastExec.DISABLED; }
          public static void setFastWriteExec(FastExec newFastWrites) { fastWriteExec = newFastWrites; }
  
          private static boolean dataStoreDetectsFutureReads;
          public static boolean dataStoreDetectsFutureReads() { return dataStoreDetectsFutureReads; }
          public static void setDataStoreDetectsFutureReads(boolean newDataStoreDetectsFutureReads) { dataStoreDetectsFutureReads = newDataStoreDetectsFutureReads; }
      }
  }
  