From a6a00fe164b8ea512bf5732a5de4559c401696e5 Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Tue, 4 Jun 2024 21:34:35 +0100 Subject: [PATCH 001/189] Nerf the thrall migration thread; very noisy. --- thrall/app/lib/MigrationSourceWithSender.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thrall/app/lib/MigrationSourceWithSender.scala b/thrall/app/lib/MigrationSourceWithSender.scala index 32f45af519e..389f9b2ce52 100644 --- a/thrall/app/lib/MigrationSourceWithSender.scala +++ b/thrall/app/lib/MigrationSourceWithSender.scala @@ -35,7 +35,7 @@ object MigrationSourceWithSender extends GridLogging { // emits MigrationRequest val scrollingIdsSource = Source.repeat(()) - .throttle(1, per = 1.second) + .throttle(1, per = 1.minute) .statefulMapConcat(() => { // This Pekko-provided stage is explicitly provided as a way to safely wrap around mutable state. // Required here to keep a marker of the current search scroll. Scrolling prevents the From 0c537476e07b1e74d11c3a8ac3f0b7632c9f582c Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Sat, 18 May 2024 11:16:41 +0100 Subject: [PATCH 002/189] Delete SyncChecker; not needed yet --- thrall/app/ThrallComponents.scala | 7 -- thrall/app/lib/SyncChecker.scala | 139 ------------------------------ 2 files changed, 146 deletions(-) diff --git a/thrall/app/ThrallComponents.scala b/thrall/app/ThrallComponents.scala index 01fcbee1038..de8a3f08017 100644 --- a/thrall/app/ThrallComponents.scala +++ b/thrall/app/ThrallComponents.scala @@ -72,13 +72,6 @@ class ThrallComponents(context: Context) extends GridComponents(context, new Thr val s3 = S3Ops.buildS3Client(config) val s3Vectors = new S3Vectors(config) - val syncChecker = new SyncChecker( - s3, - es, - config.imageBucket, - actorSystem - ) - val syncCheckerStream: Future[Done] = syncChecker.run() val softDeletedMetadataTable = new SoftDeletedMetadataTable(config) val maybeCustomReapableEligibility = config.maybeReapableEligibilityClass(applicationLifecycle) diff --git a/thrall/app/lib/SyncChecker.scala b/thrall/app/lib/SyncChecker.scala index 112a04325f2..e69de29bb2d 100644 --- a/thrall/app/lib/SyncChecker.scala +++ b/thrall/app/lib/SyncChecker.scala @@ -1,139 +0,0 @@ -package lib - -import org.apache.pekko.Done -import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.stream.{Materializer, RestartSettings} -import org.apache.pekko.stream.scaladsl.{RestartSource, Source} -import com.gu.mediaservice.lib.elasticsearch.InProgress -import com.gu.mediaservice.lib.logging.{GridLogging, LogMarker, MarkerMap} -import lib.elasticsearch.ElasticSearch -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.ListObjectsV2Request - -import scala.jdk.CollectionConverters._ -import scala.concurrent.duration.DurationInt -import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} -import scala.util.{Failure, Success} - -sealed trait SyncCheckJob {} -case class Prefix(prefix: String) extends SyncCheckJob -case object Other extends SyncCheckJob - -class SyncChecker( - s3: S3Client, - es: ElasticSearch, - imageBucketName: String, - actorSystem: ActorSystem -) extends GridLogging { - - private val mat = Materializer.matFromSystem(actorSystem) - private implicit val dispatcher: ExecutionContext = actorSystem.getDispatcher - - private val expectedAlphabet = "0123456789abcdef".split("").toSeq - private val prefixes: Seq[SyncCheckJob] = (for { - x <- expectedAlphabet - y <- expectedAlphabet - z <- expectedAlphabet - } yield Prefix(s"$x$y$z")) :+ Other - - private def paginatedListObjects(request: ListObjectsV2Request.Builder, maybeContinuationToken: Option[String] = None): Future[Seq[String]] = { - Future { - val fullRequest = maybeContinuationToken match { - case Some(token) => request.continuationToken(token) - case None => request - } - val result = s3.listObjectsV2(fullRequest.build()) - val keys = result.contents().asScala.toSeq.map(_.key().split("/").last) - val nextContinuationToken = Option(result.nextContinuationToken()) - keys -> nextContinuationToken - } flatMap { - case (keys, None) => Future.successful(keys) - case (keys, Some(token)) => - for { extraKeys <- paginatedListObjects(request, Some(token)) } yield keys ++ extraKeys - } - } - - private def listFilesWithPrefix(prefix: String): Future[Seq[String]] = { - val request = ListObjectsV2Request.builder().bucket(imageBucketName).prefix(prefix) - paginatedListObjects(request) - } - private def listFilesAtRoot(): Future[Seq[String]] = { - val request = ListObjectsV2Request.builder().bucket(imageBucketName).delimiter("/") - paginatedListObjects(request) - } - - private def checkPrefix(prefix: String)(implicit logMarker: LogMarker): Future[Unit] = { - val esIdsFetch: Future[Set[String]] = es.listImageIdsWithPrefix(prefix).map(_.toSet) - - val s3Prefix = prefix.split("").mkString("/") - // TODO String? or more complex? - val s3IdsFetch: Future[Set[String]] = listFilesWithPrefix(s3Prefix).map(_.toSet) - - for { - esIds <- esIdsFetch - s3Ids <- s3IdsFetch - } yield { - val extrasInS3 = s3Ids diff esIds - batchedLogs(s"extra images in S3 under prefix $s3Prefix", extrasInS3) - - val extrasInEs = esIds diff s3Ids - batchedLogs(s"extra images in Elasticsearch under prefix $prefix", extrasInEs) - } - } - - private def checkUnprefixed()(implicit logMarker: LogMarker): Future[Unit] = { - val s3IdsAtRootFetch = listFilesAtRoot() - val s3IdsAtSlashFetch = listFilesWithPrefix("/") - // TODO fetch all objects at unexpected locations ie. not at /^([0-9a-f]/){6}[0-9a-f]{34}$/ - // but that's a lot of prefixes to trawl. better suited to something making use of the s3 inventory that we have? - - val esIdsBadFormatFetch = es.listImageIdsWithUnexpectedFormat() - - for { - s3IdsAtRoot <- s3IdsAtRootFetch - s3IdsAtSlash <- s3IdsAtSlashFetch - esIdsBadFormat <- esIdsBadFormatFetch - } yield { - batchedLogs("in S3 root", s3IdsAtRoot) - - batchedLogs("in S3 under '/'", s3IdsAtSlash) - - batchedLogs("in Elasticsearch with unexpected id formats", esIdsBadFormat) - } - } - - private def batchedLogs(problem: String, ids: Iterable[String])(implicit logMarker: LogMarker): Unit = { - ids.grouped(250).foreach(group => - logger.warn(logMarker, s"SyncChecker found ${group.size} images $problem: ${group.mkString(",")}") - ) - } - - private val restartSettings = RestartSettings( - minBackoff = 5.seconds, - maxBackoff = 30.minutes, - randomFactor = 0.1d - ) - - private def createStream() = RestartSource.onFailuresWithBackoff(restartSettings)(() => - Source.cycle(() => prefixes.iterator) - .throttle(1, per = 5.seconds) - .filterNot(_ => es.migrationIsInProgress) - .mapAsync(parallelism = 1) { - case Prefix(prefix) => checkPrefix(prefix)(MarkerMap()) - case Other => checkUnprefixed()(MarkerMap()) - } - ) - - def run(): Future[Done] = { - val stream = createStream().run()(mat) - - logger.info("SyncChecker stream started") - - stream.onComplete { - case Failure(exception) => logger.error("SyncChecker stream completed with failure", exception) - case Success(_) => logger.info("SyncChecker stream completed with done, probably shutting down") - } - - stream - } -} From 07dd5c71bd9887950b50a6355ba47068e246a80e Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Sat, 18 May 2024 11:17:38 +0100 Subject: [PATCH 003/189] Delete Scripts; not needed yet and difficult. --- scripts/README.md | 86 -- scripts/cleanup-s3/README.md | 40 - scripts/cleanup-s3/delete-images-by-prefix.sh | 55 - scripts/cleanup-s3/images-in-root.sh | 17 - .../cleanup-s3/images-with-empty-prefix.sh | 10 - scripts/embedder-deploy/build.sh | 28 - scripts/embedder-deploy/cdk.sh | 11 - .../embedder-deploy/get-s3-vector-by-key.sh | 35 - scripts/embedder-deploy/send-message-batch.sh | 48 - scripts/embedder-deploy/send-message.sh | 36 - scripts/mass-deletion/README.md | 43 - scripts/mass-deletion/mass-deletion.sh | 53 - scripts/sample-images/README.md | 18 - .../grid-sample-images-config.json.template | 10 - scripts/sample-images/index.js | 72 - scripts/sample-images/package-lock.json | 168 --- scripts/sample-images/package.json | 18 - .../main/java/com/gu/typesafe/LICENSE-2.0.txt | 203 --- .../java/com/gu/typesafe/config/Config.java | 1139 --------------- .../gu/typesafe/config/ConfigBeanFactory.java | 49 - .../gu/typesafe/config/ConfigException.java | 460 ------ .../com/gu/typesafe/config/ConfigFactory.java | 1258 ----------------- .../typesafe/config/ConfigIncludeContext.java | 56 - .../gu/typesafe/config/ConfigIncluder.java | 50 - .../config/ConfigIncluderClasspath.java | 25 - .../typesafe/config/ConfigIncluderFile.java | 27 - .../gu/typesafe/config/ConfigIncluderURL.java | 27 - .../com/gu/typesafe/config/ConfigList.java | 48 - .../config/ConfigLoadingStrategy.java | 20 - .../gu/typesafe/config/ConfigMemorySize.java | 96 -- .../gu/typesafe/config/ConfigMergeable.java | 72 - .../com/gu/typesafe/config/ConfigObject.java | 135 -- .../com/gu/typesafe/config/ConfigOrigin.java | 118 -- .../typesafe/config/ConfigOriginFactory.java | 68 - .../typesafe/config/ConfigParseOptions.java | 242 ---- .../gu/typesafe/config/ConfigParseable.java | 45 - .../typesafe/config/ConfigRenderOptions.java | 214 --- .../typesafe/config/ConfigResolveOptions.java | 176 --- .../gu/typesafe/config/ConfigResolver.java | 38 - .../com/gu/typesafe/config/ConfigSyntax.java | 36 - .../com/gu/typesafe/config/ConfigUtil.java | 83 -- .../com/gu/typesafe/config/ConfigValue.java | 122 -- .../typesafe/config/ConfigValueFactory.java | 153 -- .../gu/typesafe/config/ConfigValueType.java | 12 - .../config/DefaultConfigLoadingStrategy.java | 14 - .../java/com/gu/typesafe/config/Optional.java | 14 - .../config/impl/AbstractConfigNode.java | 30 - .../config/impl/AbstractConfigNodeValue.java | 11 - .../config/impl/AbstractConfigObject.java | 217 --- .../config/impl/AbstractConfigValue.java | 410 ------ .../com/gu/typesafe/config/impl/BadMap.java | 137 -- .../typesafe/config/impl/ConfigBeanImpl.java | 299 ---- .../typesafe/config/impl/ConfigBoolean.java | 47 - .../config/impl/ConfigConcatenation.java | 293 ---- .../config/impl/ConfigDelayedMerge.java | 342 ----- .../config/impl/ConfigDelayedMergeObject.java | 326 ----- .../config/impl/ConfigDocumentParser.java | 712 ---------- .../gu/typesafe/config/impl/ConfigDouble.java | 61 - .../gu/typesafe/config/impl/ConfigImpl.java | 539 ------- .../typesafe/config/impl/ConfigImplUtil.java | 302 ---- .../config/impl/ConfigIncludeKind.java | 5 - .../gu/typesafe/config/impl/ConfigInt.java | 61 - .../gu/typesafe/config/impl/ConfigLong.java | 61 - .../typesafe/config/impl/ConfigNodeArray.java | 14 - .../config/impl/ConfigNodeComment.java | 17 - .../config/impl/ConfigNodeComplexValue.java | 52 - .../config/impl/ConfigNodeConcatenation.java | 14 - .../typesafe/config/impl/ConfigNodeField.java | 78 - .../config/impl/ConfigNodeInclude.java | 46 - .../config/impl/ConfigNodeObject.java | 281 ---- .../typesafe/config/impl/ConfigNodePath.java | 52 - .../typesafe/config/impl/ConfigNodeRoot.java | 67 - .../config/impl/ConfigNodeSimpleValue.java | 39 - .../config/impl/ConfigNodeSingleToken.java | 21 - .../gu/typesafe/config/impl/ConfigNull.java | 58 - .../gu/typesafe/config/impl/ConfigNumber.java | 110 -- .../gu/typesafe/config/impl/ConfigParser.java | 422 ------ .../typesafe/config/impl/ConfigReference.java | 161 --- .../gu/typesafe/config/impl/ConfigString.java | 90 -- .../gu/typesafe/config/impl/Container.java | 27 - .../config/impl/DefaultTransformer.java | 128 -- .../gu/typesafe/config/impl/FromMapMode.java | 8 - .../gu/typesafe/config/impl/FullIncluder.java | 14 - .../com/gu/typesafe/config/impl/MemoKey.java | 44 - .../typesafe/config/impl/MergeableValue.java | 9 - .../gu/typesafe/config/impl/OriginType.java | 9 - .../gu/typesafe/config/impl/Parseable.java | 873 ------------ .../com/gu/typesafe/config/impl/Path.java | 232 --- .../gu/typesafe/config/impl/PathBuilder.java | 60 - .../gu/typesafe/config/impl/PathParser.java | 281 ---- .../config/impl/PropertiesParser.java | 210 --- .../config/impl/ReplaceableMergeStack.java | 14 - .../typesafe/config/impl/ResolveContext.java | 242 ---- .../gu/typesafe/config/impl/ResolveMemos.java | 28 - .../typesafe/config/impl/ResolveResult.java | 43 - .../typesafe/config/impl/ResolveSource.java | 350 ----- .../typesafe/config/impl/ResolveStatus.java | 26 - .../config/impl/SerializedConfigValue.java | 528 ------- .../gu/typesafe/config/impl/SimpleConfig.java | 1185 ---------------- .../config/impl/SimpleConfigDocument.java | 63 - .../config/impl/SimpleConfigList.java | 460 ------ .../config/impl/SimpleConfigObject.java | 674 --------- .../config/impl/SimpleConfigOrigin.java | 575 -------- .../config/impl/SimpleIncludeContext.java | 51 - .../typesafe/config/impl/SimpleIncluder.java | 293 ---- .../config/impl/SubstitutionExpression.java | 49 - .../com/gu/typesafe/config/impl/Token.java | 87 -- .../gu/typesafe/config/impl/TokenType.java | 24 - .../gu/typesafe/config/impl/Tokenizer.java | 695 --------- .../com/gu/typesafe/config/impl/Tokens.java | 521 ------- .../gu/typesafe/config/impl/Unmergeable.java | 16 - .../com/gu/typesafe/config/impl/package.html | 25 - .../java/com/gu/typesafe/config/package.html | 58 - .../config/parser/ConfigDocument.java | 82 -- .../config/parser/ConfigDocumentFactory.java | 94 -- .../gu/typesafe/config/parser/ConfigNode.java | 35 - .../gu/typesafe/config/parser/package.html | 33 - scripts/src/main/resources/logback.xml | 13 - .../lib/JsonValueCodecJsValue.scala | 107 -- .../scripts/BackfillEditLastModified.scala | 188 --- .../mediaservice/scripts/BucketMetadata.scala | 68 - .../scripts/BulkDeleteS3Files.scala | 68 - .../mediaservice/scripts/ConvertConfig.scala | 65 - .../scripts/DecodeComparator.scala | 39 - .../mediaservice/scripts/EnactS3Changes.scala | 196 --- .../scripts/EsImageMetadata.scala | 165 --- .../gu/mediaservice/scripts/EsScript.scala | 456 ------ .../scripts/LoadFromS3Bucket.scala | 61 - .../com/gu/mediaservice/scripts/Main.scala | 25 - .../scripts/ProposeS3Changes.scala | 254 ---- scripts/usage-reindex/reindex-usages.sh | 35 - 131 files changed, 21009 deletions(-) delete mode 100644 scripts/README.md delete mode 100644 scripts/cleanup-s3/README.md delete mode 100755 scripts/cleanup-s3/delete-images-by-prefix.sh delete mode 100755 scripts/cleanup-s3/images-in-root.sh delete mode 100755 scripts/cleanup-s3/images-with-empty-prefix.sh delete mode 100755 scripts/embedder-deploy/build.sh delete mode 100755 scripts/embedder-deploy/cdk.sh delete mode 100755 scripts/embedder-deploy/get-s3-vector-by-key.sh delete mode 100755 scripts/embedder-deploy/send-message-batch.sh delete mode 100755 scripts/embedder-deploy/send-message.sh delete mode 100644 scripts/mass-deletion/README.md delete mode 100644 scripts/mass-deletion/mass-deletion.sh delete mode 100644 scripts/sample-images/README.md delete mode 100644 scripts/sample-images/grid-sample-images-config.json.template delete mode 100755 scripts/sample-images/index.js delete mode 100644 scripts/sample-images/package-lock.json delete mode 100644 scripts/sample-images/package.json delete mode 100644 scripts/src/main/java/com/gu/typesafe/LICENSE-2.0.txt delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/Config.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigBeanFactory.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigException.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigFactory.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigIncludeContext.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigIncluder.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderClasspath.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderFile.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderURL.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigList.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigLoadingStrategy.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigMemorySize.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigMergeable.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigObject.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigOrigin.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigOriginFactory.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigParseOptions.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigParseable.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigRenderOptions.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigResolveOptions.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigResolver.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigSyntax.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigUtil.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigValueFactory.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/ConfigValueType.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/DefaultConfigLoadingStrategy.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/Optional.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNode.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNodeValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigObject.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/BadMap.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBeanImpl.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBoolean.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigConcatenation.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMerge.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMergeObject.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDocumentParser.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDouble.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImpl.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImplUtil.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigIncludeKind.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigInt.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigLong.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeArray.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComment.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComplexValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeConcatenation.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeField.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeInclude.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeObject.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodePath.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeRoot.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSimpleValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSingleToken.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNull.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNumber.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigParser.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigReference.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ConfigString.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Container.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/DefaultTransformer.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/FromMapMode.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/FullIncluder.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/MemoKey.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/MergeableValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/OriginType.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Parseable.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Path.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/PathBuilder.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/PathParser.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/PropertiesParser.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ReplaceableMergeStack.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ResolveContext.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ResolveMemos.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ResolveResult.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ResolveSource.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/ResolveStatus.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SerializedConfigValue.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfig.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigDocument.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigList.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigObject.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigOrigin.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncludeContext.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncluder.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/SubstitutionExpression.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Token.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/TokenType.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Tokenizer.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Tokens.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/Unmergeable.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/impl/package.html delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/package.html delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocument.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocumentFactory.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/parser/ConfigNode.java delete mode 100644 scripts/src/main/java/com/gu/typesafe/config/parser/package.html delete mode 100644 scripts/src/main/resources/logback.xml delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/lib/JsonValueCodecJsValue.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/BackfillEditLastModified.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/BucketMetadata.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/BulkDeleteS3Files.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/ConvertConfig.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/DecodeComparator.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/EsImageMetadata.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/EsScript.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/Main.scala delete mode 100644 scripts/src/main/scala/com/gu/mediaservice/scripts/ProposeS3Changes.scala delete mode 100644 scripts/usage-reindex/reindex-usages.sh diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 24ef9d2143b..00000000000 --- a/scripts/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Scripts - -## Updating Elasticsearch - -__TL;DR__ When you update the mapping, use [Reindex](#Reindex), -when you add a mapping, use [UpdateMapping](#UpdateMapping) - -### Reindex -On occasion you will need to update the our [Elasticsearch mappings](https://github.com/guardian/grid/blob/main/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/Mappings.scala). -Unfortunately, you need to change the mapping and then reindex the data to apply said change. -[Read more about the inspiration](http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/) - -This performs the following: -1. Creates a new index (with the new mappings) appending a version number to the new index e.g. `images_5` -2. Copies over all data from the original index to the new index using scrolling -3. Points the write alias to the new index -4. Checks if any new data has been wrote since the script started, if so copies this over as well -5. Points the read alias to the new index - -``` - $ sbt - > scripts/run Reindex -``` - -Optionally takes a DateTime string argument. Will perform reindex for documents updated *since the date provieded* - - > scripts/run Reindex FROM_TIME=016-01-28T10:55:10.232Z - -Optionally takes a new index name string argument. Will reindex into that new name instead of the default version increment - - > scripts/run Reindex NEW_INDEX=images - -### UpdateMapping -When you add a mapping e.g. You add a new field to the [image mapping](https://github.com/guardian/grid/blob/main/common-lib/src/main/scala/com/gu/mediaservice/lib/elasticsearch/Mappings.scala#L73) -you should add the mapping with this script as we are using [`strict`](http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/dynamic-mapping.html) -mappings (you cannot just add things willy nilly). Updating mappings is done in 2 steps: - -1. Set up a SSH tunnel to the AWS elasticsearch instance: `ssh -L 9200:localhost:9200 ` - -2. Run the script: -``` - $ sbt - > scripts/run UpdateMapping -``` - -Optionally takes an index name. e.g. `> scripts/run UpdateMapping images_5` - -To test the connection without making any changes to the mappings, you can run: `sbt "scripts/run GetMapping "`. - -### UpdateSettings -When you need to close the index to update the settings i.e. when you have to add / reconfigure -analysers - this is the command you can use. - -__:warning: This should only EVER be run on your local version. :warning:__ - - ``` - $ # after pausing thrall - $ sbt - > scripts/run UpdateSettings localhost - ``` - -### DownloadAllEsIds -ES doesn't provide a means of downloading all the IDs, so this script does just that and writes to file - for example a CSV file for upload to AWS Athena. - -It relies on the [`es-ssh-ssm-tunnel.sh` script](../dev/script/es-ssh-ssm-tunnel.sh). - -It's most efficient to do this as a 'scan and scroll' (see [stackoverflow.com/a/30855670](https://stackoverflow.com/a/30855670)). - - ``` - $ sbt - > scripts/run DownloadAllEsIds http://localhost:9200 /tmp/testing - ``` - -### BulkDeleteS3Files - - ``` - $ sbt - > scripts/run BulkDeleteS3Files - ``` - -Input file needs to be a CSV, with a heading row and a single column containing the S3 paths to delete from the specified bucket. - -This script groups the input IDs into 1000s so it can use the [bulk delete API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/delete-multiple-objects.html) and reports the success or failure for each S3 path to both the console but also the `auditFile` path provided (CSV output). - -Note: bulk delete API reports 'deleted' if the path is not found, so this can be run multiple times without issue (although delete markers will be created in S3 for every execution). - diff --git a/scripts/cleanup-s3/README.md b/scripts/cleanup-s3/README.md deleted file mode 100644 index 2de41357617..00000000000 --- a/scripts/cleanup-s3/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Cheap and cheerful scripts to clean up our grid imagebuckets - -## Delete function -``` -delete-images-by-prefix.sh -``` -Deletes images, if found, which match the provided prefix. - -Takes parameters: - * size - * prefix - * stage - * profile - * region - -Not intended to be invoked directly. - -## Wrapper function to delete images in the root -``` -images-in-root.sh -``` - -Takes parameters: - * size - * stage - * profile - * region - -## Wrapper function to delete images with a null first prefix (ie key beginning with /) -``` -images-with-empty-prefix.sh -``` - -Takes parameters: - * size - * stage - * profile - * region - -Continues until it successfully deletes something or runs out of prefixes to check. diff --git a/scripts/cleanup-s3/delete-images-by-prefix.sh b/scripts/cleanup-s3/delete-images-by-prefix.sh deleted file mode 100755 index 31bd3462f45..00000000000 --- a/scripts/cleanup-s3/delete-images-by-prefix.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -size=${1:-10}; shift -prefix=${1:00}; shift -stage=${1:-test}; shift -profile=${1:-media-service}; shift -region=${1:-eu-west-1}; shift - -creds="--profile $profile --region $region" -s3cmd="aws $creds s3 " -s3apicmd="aws $creds s3api " - -function getBucket { - $s3cmd ls | grep media-service-$stage-imagebucket | awk '{print $3}' -} - -function getImages { - $s3apicmd list-objects --bucket $bucket --page-size $size --max-items $size --prefix $prefix -} - -function constructAwsJson { - cat | jq ' -[ - .Contents[] - | - { "Key": .Key } -] -| -{ - "Objects": ., - "Quiet": false -} -' -} - -bucket=$(getBucket) - -echo "Deleting up to $size images in root of $bucket using profile $profile in region $region" - -tempFile=$(mktemp) -echo "Using temp file $tempFile" - -getImages | constructAwsJson > $tempFile - -empty=$(find $(dirname $tempFile) -empty -name $(basename $tempFile) | wc -l) -if [[ $empty -eq 1 ]]; then - echo "No images found to match prefix $prefix" - rc=1 -else - $s3apicmd delete-objects --bucket $bucket --delete file://$tempFile - rc=$? -fi - -rm $tempFile -exit $rc diff --git a/scripts/cleanup-s3/images-in-root.sh b/scripts/cleanup-s3/images-in-root.sh deleted file mode 100755 index d39520878a1..00000000000 --- a/scripts/cleanup-s3/images-in-root.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -size=${1:-10}; shift -stage=${1:-test}; shift -profile=${1:-media-service}; shift -region=${1:-eu-west-1}; shift - -cd $(dirname $0) - -for a in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do - for b in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do - ./delete-images-by-prefix.sh $size $a$b $stage $profile $region - if [[ $? -eq 0 ]]; then - exit - fi - done -done diff --git a/scripts/cleanup-s3/images-with-empty-prefix.sh b/scripts/cleanup-s3/images-with-empty-prefix.sh deleted file mode 100755 index 939003fbf81..00000000000 --- a/scripts/cleanup-s3/images-with-empty-prefix.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -size=${1:-10}; shift -stage=${1:-test}; shift -profile=${1:-media-service}; shift -region=${1:-eu-west-1}; shift - -cd $(dirname $0) - -./delete-images-by-prefix.sh $size '/' $stage $profile $region diff --git a/scripts/embedder-deploy/build.sh b/scripts/embedder-deploy/build.sh deleted file mode 100755 index d873bbdbcce..00000000000 --- a/scripts/embedder-deploy/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -e - -( - cd image-embedder-lambda - npm ci - # Install sharp's native bindings for the lambda target architecture (linux-arm64) - npm install --cpu=arm64 --os=linux --libc=glibc sharp - npm run build - - # Unfortunately because sharp contains native C++ code, we can't bundle - # it into the single JS file produced by esbuild. - # Instead, we have to declare it external and add it and its depedencies - # to the deploy zip ourselves. - # https://sharp.pixelplumbing.com/install/#esbuild - mkdir -p dist/node_modules/@img - cp -r node_modules/sharp dist/node_modules/ - cp -r node_modules/@img/sharp-linux-arm64 dist/node_modules/@img/ - cp -r node_modules/@img/sharp-libvips-linux-arm64 dist/node_modules/@img/ - cp -r node_modules/@img/colour dist/node_modules/@img/ - # Sharp's JS dependencies - cp -r node_modules/detect-libc dist/node_modules/ - cp -r node_modules/semver dist/node_modules/ - - cd dist - zip -r ../../image-embedder.zip . -) \ No newline at end of file diff --git a/scripts/embedder-deploy/cdk.sh b/scripts/embedder-deploy/cdk.sh deleted file mode 100755 index 06ba8a13abd..00000000000 --- a/scripts/embedder-deploy/cdk.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -e - -( - cd cdk - npm ci - npm run lint - npm run test - npm run synth -) \ No newline at end of file diff --git a/scripts/embedder-deploy/get-s3-vector-by-key.sh b/scripts/embedder-deploy/get-s3-vector-by-key.sh deleted file mode 100755 index 1b0f553c9b3..00000000000 --- a/scripts/embedder-deploy/get-s3-vector-by-key.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -set -euo pipefail - -if [ $# -lt 2 ]; then - echo "Error: Stage and vector key arguments are required" - echo "" - echo "Usage: $0 " - echo "" - echo "STAGE: test, dev, or prod" - echo "" - echo "Example:" - echo " $0 test 9e3fec5784b8e203b9ee6139477d689cc9162c9a" - exit 1 -fi - -STAGE="$1" -VECTOR_KEY="$2" - -if [[ ! "$STAGE" =~ ^(dev|test|prod)$ ]]; then - echo "Invalid stage. Please use dev, test, or prod" - exit 1 -fi - - -# Write this to stderr so we can pipe get-vectors stdout to jq ->&2 echo "Fetching vector with key: ${VECTOR_KEY} from ${STAGE}" - -aws s3vectors get-vectors \ - --vector-bucket-name image-embeddings-"${STAGE}" \ - --index-name cohere-embed-english-v3 \ - --return-data \ - --keys "${VECTOR_KEY}" \ - --return-metadata \ - --profile media-service \ - --region eu-central-1 \ No newline at end of file diff --git a/scripts/embedder-deploy/send-message-batch.sh b/scripts/embedder-deploy/send-message-batch.sh deleted file mode 100755 index 57e55e93d1a..00000000000 --- a/scripts/embedder-deploy/send-message-batch.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# This script send a batch of duplicate messages and the handler will loop through them. -# BUT the write to the S3 Vector Store will fail because we're trying to write duplicate keys! -# Right now, this script is just useful for testing the loop behaviour -# and perhaps may be useful in the future to test skipping images already embedded (when we add that) - -# Check for required arguments -if [ $# -ne 4 ]; then - echo "Usage: $0 " - exit 1 -fi - -IMAGE_ID="$1" -FILE_TYPE="$2" -S3_BUCKET="$3" -S3_KEY="$4" - -# Fixed stage -STAGE="TEST" - -QUEUE_NAME="image-embedder-${STAGE}" - -QUEUE_URL=$( - aws sqs get-queue-url \ - --queue-name "$QUEUE_NAME" \ - --query 'QueueUrl' \ - --output text \ - --profile media-service \ - --region eu-west-1 -) -# Build entries using jq -ENTRIES=$(jq -n \ - --arg imageId "$IMAGE_ID" \ - --arg fileType "$FILE_TYPE" \ - --arg s3Bucket "$S3_BUCKET" \ - --arg s3Key "$S3_KEY" \ - '[range(1;11) | { - Id: ("msg-" + (. | tostring)), - MessageBody: ({imageId: $imageId, fileType: $fileType, s3Bucket: $s3Bucket, s3Key: $s3Key} | tostring) - }]') - -aws sqs send-message-batch \ - --queue-url "$QUEUE_URL" \ - --entries "$ENTRIES" \ - --profile media-service \ - --region eu-west-1 \ No newline at end of file diff --git a/scripts/embedder-deploy/send-message.sh b/scripts/embedder-deploy/send-message.sh deleted file mode 100755 index b846919c7a8..00000000000 --- a/scripts/embedder-deploy/send-message.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Check for required arguments -if [ $# -ne 4 ]; then - echo "Usage: $0 " - exit 1 -fi - -IMAGE_ID="$1" -FILE_TYPE="$2" -S3_BUCKET="$3" -S3_KEY="$4" - -# Fixed stage -STAGE="TEST" - -QUEUE_NAME="image-embedder-${STAGE}" - -QUEUE_URL=$( - aws sqs get-queue-url \ - --queue-name "$QUEUE_NAME" \ - --query 'QueueUrl' \ - --output text \ - --profile media-service \ - --region eu-west-1 -) - -aws sqs send-message \ - --queue-url "$QUEUE_URL" \ - --message-body \ - "{\"imageId\": \"${IMAGE_ID}\", \"fileType\": \"${FILE_TYPE}\", \"s3Bucket\": \"${S3_BUCKET}\", \"s3Key\": \"${S3_KEY}\"}" \ - --profile media-service \ - --region eu-west-1 - -echo "Sent message to queue ${QUEUE_NAME}:" diff --git a/scripts/mass-deletion/README.md b/scripts/mass-deletion/README.md deleted file mode 100644 index fe08a29afac..00000000000 --- a/scripts/mass-deletion/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# mass-deletion - -The Grid services and UI have no way to perform mass deletion of images. When -this is required, you will need to perform it by interacting with the Grid API. -This script may help you to run these deletions. - -## input - -### todelete.txt - -File containing list of image IDs to be deleted, separated by newlines - -e.g. -``` -abcdef0123456789abcdef0123456789abcdef01 -10fedcba9876543210fedcba9876543210fedcba -... -``` - -### GRIDKEY - -Environment variable containing [Grid API key](/docs/03-apis/01-authentication.md#api-keys) -to use for the request (should be Internal tier) - -### GRIDDOMAIN - -Environment variable containing the grid domain (kahuna domain). - -## output - -### progress.txt - -File contains the last processed Image ID - this allows cancelling and resuming -the script if necessary. - -### complete.txt - -File containing the list of successfully deleted Image IDs. - -### errors.txt - -File containing the list of Image IDs which could not be deleted (for any -reason). diff --git a/scripts/mass-deletion/mass-deletion.sh b/scripts/mass-deletion/mass-deletion.sh deleted file mode 100644 index a95e779aea8..00000000000 --- a/scripts/mass-deletion/mass-deletion.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -if [[ -n "$GRIDKEY" || -n "$GRIDDOMAIN" ]]; then - echo "make sure to set env vars GRIDKEY and GRIDDOMAIN to run this script" - exit 1 -fi - -# input file, stores all the ids that must be deleted -del=todelete.txt -# progress file, stores the id most recently successfully deleted -prog=progress.txt -# completion file, stores all ids successfully deleted -com=complete.txt -# errors file, stores all ids that could not be deleted -errs=errors.txt - -touch $prog -touch $com -touch $errs - -all="$(wc -l $del | awk '{ print $1 }' )" -ndone=0 - -# while skipping is "yes", we'll fastforward through the input file "todelete.txt" -# until we get to the id that was in the progress file. This way we won't reattempt -# any deletions that completed in a previous run, and we can quickly resume our -# previous status. -# (If instead you do want to start again from the beginning, simply remove the progress file!) -skipping=yes -last="$(cat $prog)" - - - -while read id; do - ndone=$((ndone + 1)) - if [[ $skipping = "yes" && -n "$last" && $id != $last ]]; then - continue - elif [[ $skipping = "yes" && -n "$last" ]]; then - skipping=no - continue - else - echo -n "deleting $id ($ndone / $all)... " - if curl -fLso /dev/null -XDELETE -H "X-Gu-Media-Key: $GRIDKEY" "https://api.$GRIDDOMAIN/images/$id/hard-delete"; then - echo $id > $prog - echo $id >> $com - echo "done" - else - echo "error $id" - echo $id >> $errs - fi - sleep 0.2 - fi -done < $del diff --git a/scripts/sample-images/README.md b/scripts/sample-images/README.md deleted file mode 100644 index 7d02812fbc3..00000000000 --- a/scripts/sample-images/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# sample-images - -Upload random images from [Unsplash](https://unsplash.com/) into Grid. - -## Setup -- Install dependencies `npm install` -- Register with the [Unsplash API](https://unsplash.com/developers) to get credentials -- Create a config file in `~/.gu/grid-sample-images-config.json` using [the template](./grid-sample-images-config.json.template) - -## Running -- Run `npm run upload-sample-images` - -You can optionally define the number of images to download (default is 1). For example downloading 5 images `npm run upload-sample-images 5`. Note, developer tier credentials on Unsplash are limited to 50 requests an hour. - -## Troubleshooting -If the instance of Grid is using a self-signed certificate, you'll first need to tell node where to find it by setting the `NODE_EXTRA_CA_CERTS` environment variable. - -For example, if you're using [mkcert](https://github.com/FiloSottile/mkcert#using-the-root-with-nodejs), you can do `NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem" npm run upload-sample-images` diff --git a/scripts/sample-images/grid-sample-images-config.json.template b/scripts/sample-images/grid-sample-images-config.json.template deleted file mode 100644 index 4c15fd7d104..00000000000 --- a/scripts/sample-images/grid-sample-images-config.json.template +++ /dev/null @@ -1,10 +0,0 @@ -{ - "unsplash": { - "accessKey": "", - "appSecret": "" - }, - "grid": { - "apiKey": "", - "mediaApi": "" - } -} diff --git a/scripts/sample-images/index.js b/scripts/sample-images/index.js deleted file mode 100755 index e7670134fed..00000000000 --- a/scripts/sample-images/index.js +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const fs = require('fs'); - -require('es6-promise').polyfill(); -require('isomorphic-fetch'); -const Unsplash = require('unsplash-js').default; - -const DEFAULT_NUMBER_OF_IMAGES = 1; -const numberOfImages = process.argv[2] || DEFAULT_NUMBER_OF_IMAGES; - -const RATE_LIMIT_REMAINING = 'x-ratelimit-remaining'; -const RATE_LIMIT_EXCEEDED = 'unsplash-rate-limit-hit'; - -const configFile = path.join(process.env.HOME, '.gu', 'grid-sample-images-config.json'); -const fileExists = fs.existsSync(configFile); - -if (!fileExists) { - throw `Config file missing. Please create ${configFile}.` -} - -const {unsplash: {accessKey, appSecret}, grid: {apiKey, mediaApi}} = require(configFile); - -const unsplash = new Unsplash({ - applicationId: accessKey, - secret: appSecret -}); - -async function importImage({url}) { - const apiRoot = await fetch(mediaApi, { - method: 'GET', - headers: { 'X-Gu-Media-Key': apiKey } - }).then(_ => _.json()); - - const loaderUrl = apiRoot.links.find(_ => _.rel === 'loader').href; - - return fetch(`${loaderUrl}/imports?uri=${url}`, { - method: 'POST', - headers: { 'X-Gu-Media-Key': apiKey } - }).then(_ => _.json()); -} - -async function downloadImages() { - const randomRequest = await unsplash.photos.getRandomPhoto({count: numberOfImages}); - - const { headers } = randomRequest; - const limitRemaining = headers.get(RATE_LIMIT_REMAINING); - - if (limitRemaining === 0 || limitRemaining < numberOfImages) { - return [Promise.reject(RATE_LIMIT_EXCEEDED)]; - } - - const photos = await randomRequest.json(); - - return Promise.all(photos.map(photo => ( - unsplash.photos.downloadPhoto(photo) - .then(_ => _.json()) - .then(importImage) - ))); -} - -try { - downloadImages() - .then(images => { - console.log(`Uploaded ${images.length} images to Grid`); - console.log(images); - }) - .catch(console.error); -} catch (err) { - console.error(err); -} diff --git a/scripts/sample-images/package-lock.json b/scripts/sample-images/package-lock.json deleted file mode 100644 index 7f1193a114b..00000000000 --- a/scripts/sample-images/package-lock.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "name": "sample-images", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "sample-images", - "version": "0.0.0", - "license": "ISC", - "dependencies": { - "es6-promise": "^4.2.8", - "isomorphic-fetch": "^3.0.0", - "unsplash-js": "^7.0.15" - }, - "devDependencies": {} - }, - "node_modules/@types/content-type": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.5.tgz", - "integrity": "sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==" - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/unsplash-js": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/unsplash-js/-/unsplash-js-7.0.15.tgz", - "integrity": "sha512-WGqKp9wl2m2tAUPyw2eMZs/KICR+A52tCaRapzVXWxkA4pjHqsaGwiJXTEW7hBy4Pu0QmP6KxTt2jST3tluawA==", - "dependencies": { - "@types/content-type": "^1.1.3", - "content-type": "^1.0.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - }, - "dependencies": { - "@types/content-type": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/content-type/-/content-type-1.1.5.tgz", - "integrity": "sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "unsplash-js": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/unsplash-js/-/unsplash-js-7.0.15.tgz", - "integrity": "sha512-WGqKp9wl2m2tAUPyw2eMZs/KICR+A52tCaRapzVXWxkA4pjHqsaGwiJXTEW7hBy4Pu0QmP6KxTt2jST3tluawA==", - "requires": { - "@types/content-type": "^1.1.3", - "content-type": "^1.0.4" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } -} diff --git a/scripts/sample-images/package.json b/scripts/sample-images/package.json deleted file mode 100644 index 3a9c5911828..00000000000 --- a/scripts/sample-images/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sample-images", - "version": "0.0.0", - "description": "Upload images from Unsplash into Grid for demonstration", - "main": "index.js", - "dependencies": { - "es6-promise": "^4.2.8", - "isomorphic-fetch": "^3.0.0", - "unsplash-js": "^7.0.15" - }, - "devDependencies": {}, - "scripts": { - "upload-sample-images": "node index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC" -} diff --git a/scripts/src/main/java/com/gu/typesafe/LICENSE-2.0.txt b/scripts/src/main/java/com/gu/typesafe/LICENSE-2.0.txt deleted file mode 100644 index b6129c0abd4..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/LICENSE-2.0.txt +++ /dev/null @@ -1,203 +0,0 @@ -License below for the shaded copy of Typesafe Config from https://github.com/lightbend/config: - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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 - - https://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. diff --git a/scripts/src/main/java/com/gu/typesafe/config/Config.java b/scripts/src/main/java/com/gu/typesafe/config/Config.java deleted file mode 100644 index 70e808f92e7..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/Config.java +++ /dev/null @@ -1,1139 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.time.Duration; -import java.time.Period; -import java.time.temporal.TemporalAmount; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * An immutable map from config paths to config values. Paths are dot-separated - * expressions such as foo.bar.baz. Values are as in JSON - * (booleans, strings, numbers, lists, or objects), represented by - * {@link com.gu.typesafe.config.ConfigValue} instances. Values accessed through the - * Config interface are never null. - * - *

- * {@code Config} is an immutable object and thus safe to use from multiple - * threads. There's never a need for "defensive copies." - * - *

- * Fundamental operations on a {@code Config} include getting configuration - * values, resolving substitutions with {@link Config#resolve()}, and - * merging configs using {@link Config#withFallback(com.gu.typesafe.config.ConfigMergeable)}. - * - *

- * All operations return a new immutable {@code Config} rather than modifying - * the original instance. - * - *

- * Examples - * - *

- * You can find an example app and library on - * GitHub. Also be sure to read the package overview which - * describes the big picture as shown in those examples. - * - *

- * Paths, keys, and Config vs. ConfigObject - * - *

- * Config is a view onto a tree of {@link com.gu.typesafe.config.ConfigObject}; the - * corresponding object tree can be found through {@link Config#root()}. - * ConfigObject is a map from config keys, rather than - * paths, to config values. Think of ConfigObject as a JSON object - * and Config as a configuration API. - * - *

- * The API tries to consistently use the terms "key" and "path." A key is a key - * in a JSON object; it's just a string that's the key in a map. A "path" is a - * parseable expression with a syntax and it refers to a series of keys. Path - * expressions are described in the spec for - * Human-Optimized Config Object Notation. In brief, a path is - * period-separated so "a.b.c" looks for key c in object b in object a in the - * root object. Sometimes double quotes are needed around special characters in - * path expressions. - * - *

- * The API for a {@code Config} is in terms of path expressions, while the API - * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} - * is a one-level map from paths to values, while a - * {@code ConfigObject} is a tree of nested maps from keys to values. - * - *

- * Use {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath} to convert - * between path expressions and individual path elements (keys). - * - *

- * Another difference between {@code Config} and {@code ConfigObject} is that - * conceptually, {@code ConfigValue}s with a {@link com.gu.typesafe.config.ConfigValue#valueType() - * valueType()} of {@link ConfigValueType#NULL NULL} exist in a - * {@code ConfigObject}, while a {@code Config} treats null values as if they - * were missing. (With the exception of two methods: {@link Config#hasPathOrNull} - * and {@link Config#getIsNull} let you detect null values.) - * - *

- * Getting configuration values - * - *

- * The "getters" on a {@code Config} all work in the same way. They never return - * null, nor do they return a {@code ConfigValue} with - * {@link com.gu.typesafe.config.ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL - * NULL}. Instead, they throw {@link com.gu.typesafe.config.ConfigException.Missing} if the value is - * completely absent or set to null. If the value is set to null, a subtype of - * {@code ConfigException.Missing} called {@link com.gu.typesafe.config.ConfigException.Null} will be - * thrown. {@link com.gu.typesafe.config.ConfigException.WrongType} will be thrown anytime you ask for - * a type and the value has an incompatible type. Reasonable type conversions - * are performed for you though. - * - *

- * Iteration - * - *

- * If you want to iterate over the contents of a {@code Config}, you can get its - * {@code ConfigObject} with {@link #root()}, and then iterate over the - * {@code ConfigObject} (which implements java.util.Map). Or, you - * can use {@link #entrySet()} which recurses the object tree for you and builds - * up a Set of all path-value pairs where the value is not null. - * - *

- * Resolving substitutions - * - *

- * Substitutions are the ${foo.bar} syntax in config - * files, described in the specification. Resolving substitutions replaces these references with real - * values. - * - *

- * Before using a {@code Config} it's necessary to call {@link Config#resolve()} - * to handle substitutions (though {@link com.gu.typesafe.config.ConfigFactory#load()} and similar - * methods will do the resolve for you already). - * - *

- * Merging - * - *

- * The full Config for your application can be constructed using - * the associative operation {@link Config#withFallback(com.gu.typesafe.config.ConfigMergeable)}. If - * you use {@link com.gu.typesafe.config.ConfigFactory#load()} (recommended), it merges system - * properties over the top of application.conf over the top of - * reference.conf, using withFallback. You can add in - * additional sources of configuration in the same way (usually, custom layers - * should go either just above or just below application.conf, - * keeping reference.conf at the bottom and system properties at - * the top). - * - *

- * Serialization - * - *

- * Convert a Config to a JSON or HOCON string by calling - * {@link com.gu.typesafe.config.ConfigObject#render()} on the root object, - * myConfig.root().render(). There's also a variant - * {@link com.gu.typesafe.config.ConfigObject#render(com.gu.typesafe.config.ConfigRenderOptions)} which allows you to control - * the format of the rendered string. (See {@link ConfigRenderOptions}.) Note - * that Config does not remember the formatting of the original - * file, so if you load, modify, and re-save a config file, it will be - * substantially reformatted. - * - *

- * As an alternative to {@link com.gu.typesafe.config.ConfigObject#render()}, the - * toString() method produces a debug-output-oriented - * representation (which is not valid JSON). - * - *

- * Java serialization is supported as well for Config and all - * subtypes of ConfigValue. - * - *

- * This is an interface but don't implement it yourself - * - *

- * Do not implement {@code Config}; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface Config extends com.gu.typesafe.config.ConfigMergeable { - /** - * Gets the {@code Config} as a tree of {@link com.gu.typesafe.config.ConfigObject}. This is a - * constant-time operation (it is not proportional to the number of values - * in the {@code Config}). - * - * @return the root object in the configuration - */ - com.gu.typesafe.config.ConfigObject root(); - - /** - * Gets the origin of the {@code Config}, which may be a file, or a file - * with a line number, or just a descriptive phrase. - * - * @return the origin of the {@code Config} for use in error messages - */ - ConfigOrigin origin(); - - @Override - Config withFallback(ConfigMergeable other); - - /** - * Returns a replacement config with all substitutions (the - * ${foo.bar} syntax, see the - * spec) resolved. Substitutions are looked up using this - * Config as the root object, that is, a substitution - * ${foo.bar} will be replaced with the result of - * getValue("foo.bar"). - * - *

- * This method uses {@link com.gu.typesafe.config.ConfigResolveOptions#defaults()}, there is - * another variant {@link Config#resolve(com.gu.typesafe.config.ConfigResolveOptions)} which lets - * you specify non-default options. - * - *

- * A given {@link Config} must be resolved before using it to retrieve - * config values, but ideally should be resolved one time for your entire - * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some - * substitutions that could have resolved with all fallbacks available may - * not resolve, which will be potentially confusing for your application's - * users. - * - *

- * resolve() should be invoked on root config objects, rather - * than on a subtree (a subtree is the result of something like - * config.getConfig("foo")). The problem with - * resolve() on a subtree is that substitutions are relative to - * the root of the config and the subtree will have no way to get values - * from the root. For example, if you did - * config.getConfig("foo").resolve() on the below config file, - * it would not work: - * - *

-     *   common-value = 10
-     *   foo {
-     *      whatever = ${common-value}
-     *   }
-     * 
- * - *

- * Many methods on {@link com.gu.typesafe.config.ConfigFactory} such as - * {@link ConfigFactory#load()} automatically resolve the loaded - * Config on the loaded stack of config files. - * - *

- * Resolving an already-resolved config is a harmless no-op, but again, it - * is best to resolve an entire stack of fallbacks (such as all your config - * files combined) rather than resolving each one individually. - * - * @return an immutable object with substitutions resolved - * @throws com.gu.typesafe.config.ConfigException.UnresolvedSubstitution - * if any substitutions refer to nonexistent paths - * @throws com.gu.typesafe.config.ConfigException - * some other config exception if there are other problems - */ - Config resolve(); - - /** - * Like {@link Config#resolve()} but allows you to specify non-default - * options. - * - * @param options - * resolve options - * @return the resolved Config (may be only partially resolved if options are set to allow unresolved) - */ - Config resolve(com.gu.typesafe.config.ConfigResolveOptions options); - - /** - * Checks whether the config is completely resolved. After a successful call - * to {@link Config#resolve()} it will be completely resolved, but after - * calling {@link Config#resolve(com.gu.typesafe.config.ConfigResolveOptions)} with - * allowUnresolved set in the options, it may or may not be - * completely resolved. A newly-loaded config may or may not be completely - * resolved depending on whether there were substitutions present in the - * file. - * - * @return true if there are no unresolved substitutions remaining in this - * configuration. - * @since 1.2.0 - */ - boolean isResolved(); - - /** - * Like {@link Config#resolve()} except that substitution values are looked - * up in the given source, rather than in this instance. This is a - * special-purpose method which doesn't make sense to use in most cases; - * it's only needed if you're constructing some sort of app-specific custom - * approach to configuration. The more usual approach if you have a source - * of substitution values would be to merge that source into your config - * stack using {@link Config#withFallback} and then resolve. - *

- * Note that this method does NOT look in this instance for substitution - * values. If you want to do that, you could either merge this instance into - * your value source using {@link Config#withFallback}, or you could resolve - * multiple times with multiple sources (using - * {@link com.gu.typesafe.config.ConfigResolveOptions#setAllowUnresolved(boolean)} so the partial - * resolves don't fail). - * - * @param source - * configuration to pull values from - * @return an immutable object with substitutions resolved - * @throws com.gu.typesafe.config.ConfigException.UnresolvedSubstitution - * if any substitutions refer to paths which are not in the - * source - * @throws com.gu.typesafe.config.ConfigException - * some other config exception if there are other problems - * @since 1.2.0 - */ - Config resolveWith(Config source); - - /** - * Like {@link Config#resolveWith(Config)} but allows you to specify - * non-default options. - * - * @param source - * source configuration to pull values from - * @param options - * resolve options - * @return the resolved Config (may be only partially resolved - * if options are set to allow unresolved) - * @since 1.2.0 - */ - Config resolveWith(Config source, ConfigResolveOptions options); - - /** - * Validates this config against a reference config, throwing an exception - * if it is invalid. The purpose of this method is to "fail early" with a - * comprehensive list of problems; in general, anything this method can find - * would be detected later when trying to use the config, but it's often - * more user-friendly to fail right away when loading the config. - * - *

- * Using this method is always optional, since you can "fail late" instead. - * - *

- * You must restrict validation to paths you "own" (those whose meaning are - * defined by your code module). If you validate globally, you may trigger - * errors about paths that happen to be in the config but have nothing to do - * with your module. It's best to allow the modules owning those paths to - * validate them. Also, if every module validates only its own stuff, there - * isn't as much redundant work being done. - * - *

- * If no paths are specified in checkValid()'s parameter list, - * validation is for the entire config. - * - *

- * If you specify paths that are not in the reference config, those paths - * are ignored. (There's nothing to validate.) - * - *

- * Here's what validation involves: - * - *

    - *
  • All paths found in the reference config must be present in this - * config or an exception will be thrown. - *
  • - * Some changes in type from the reference config to this config will cause - * an exception to be thrown. Not all potential type problems are detected, - * in particular it's assumed that strings are compatible with everything - * except objects and lists. This is because string types are often "really" - * some other type (system properties always start out as strings, or a - * string like "5ms" could be used with {@link #getMilliseconds}). Also, - * it's allowed to set any type to null or override null with any type. - *
  • - * Any unresolved substitutions in this config will cause a validation - * failure; both the reference config and this config should be resolved - * before validation. If the reference config is unresolved, it's a bug in - * the caller of this method. - *
- * - *

- * If you want to allow a certain setting to have a flexible type (or - * otherwise want validation to be looser for some settings), you could - * either remove the problematic setting from the reference config provided - * to this method, or you could intercept the validation exception and - * screen out certain problems. Of course, this will only work if all other - * callers of this method are careful to restrict validation to their own - * paths, as they should be. - * - *

- * If validation fails, the thrown exception contains a list of all problems - * found. See {@link com.gu.typesafe.config.ConfigException.ValidationFailed#problems}. The - * exception's getMessage() will have all the problems - * concatenated into one huge string, as well. - * - *

- * Again, checkValid() can't guess every domain-specific way a - * setting can be invalid, so some problems may arise later when attempting - * to use the config. checkValid() is limited to reporting - * generic, but common, problems such as missing settings and blatant type - * incompatibilities. - * - * @param reference - * a reference configuration - * @param restrictToPaths - * only validate values underneath these paths that your code - * module owns and understands - * @throws com.gu.typesafe.config.ConfigException.ValidationFailed - * if there are any validation issues - * @throws com.gu.typesafe.config.ConfigException.NotResolved - * if this config is not resolved - * @throws com.gu.typesafe.config.ConfigException.BugOrBroken - * if the reference config is unresolved or caller otherwise - * misuses the API - */ - void checkValid(Config reference, String... restrictToPaths); - - /** - * Checks whether a value is present and non-null at the given path. This - * differs in two ways from {@code Map.containsKey()} as implemented by - * {@link com.gu.typesafe.config.ConfigObject}: it looks for a path expression, not a key; and it - * returns false for null values, while {@code containsKey()} returns true - * indicating that the object contains a null value for the key. - * - *

- * If a path exists according to {@link #hasPath(String)}, then - * {@link #getValue(String)} will never throw an exception. However, the - * typed getters, such as {@link #getInt(String)}, will still throw if the - * value is not convertible to the requested type. - * - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * the path expression - * @return true if a non-null value is present at the path - * @throws com.gu.typesafe.config.ConfigException.BadPath - * if the path expression is invalid - */ - boolean hasPath(String path); - - /** - * Checks whether a value is present at the given path, even - * if the value is null. Most of the getters on - * Config will throw if you try to get a null - * value, so if you plan to call {@link #getValue(String)}, - * {@link #getInt(String)}, or another getter you may want to - * use plain {@link #hasPath(String)} rather than this method. - * - *

- * To handle all three cases (unset, null, and a non-null value) - * the code might look like: - *


-     * if (config.hasPathOrNull(path)) {
-     *     if (config.getIsNull(path)) {
-     *        // handle null setting
-     *     } else {
-     *        // get and use non-null setting
-     *     }
-     * } else {
-     *     // handle entirely unset path
-     * }
-     * 
- * - *

However, the usual thing is to allow entirely unset - * paths to be a bug that throws an exception (because you set - * a default in your reference.conf), so in that - * case it's OK to call {@link #getIsNull(String)} without - * checking hasPathOrNull first. - * - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * the path expression - * @return true if a value is present at the path, even if the value is null - * @throws com.gu.typesafe.config.ConfigException.BadPath - * if the path expression is invalid - */ - boolean hasPathOrNull(String path); - - /** - * Returns true if the {@code Config}'s root object contains no key-value - * pairs. - * - * @return true if the configuration is empty - */ - boolean isEmpty(); - - /** - * Returns the set of path-value pairs, excluding any null values, found by - * recursing {@link #root() the root object}. Note that this is very - * different from root().entrySet() which returns the set of - * immediate-child keys in the root object and includes null values. - *

- * Entries contain path expressions meaning there may be quoting - * and escaping involved. Parse path expressions with - * {@link com.gu.typesafe.config.ConfigUtil#splitPath}. - *

- * Because a Config is conceptually a single-level map from - * paths to values, there will not be any {@link com.gu.typesafe.config.ConfigObject} values in the - * entries (that is, all entries represent leaf nodes). Use - * {@link com.gu.typesafe.config.ConfigObject} rather than Config if you want a tree. - * (OK, this is a slight lie: Config entries may contain - * {@link com.gu.typesafe.config.ConfigList} and the lists may contain objects. But no objects are - * directly included as entry values.) - * - * @return set of paths with non-null values, built up by recursing the - * entire tree of {@link com.gu.typesafe.config.ConfigObject} and creating an entry for - * each leaf value. - */ - Set> entrySet(); - - /** - * Checks whether a value is set to null at the given path, - * but throws an exception if the value is entirely - * unset. This method will not throw if {@link - * #hasPathOrNull(String)} returned true for the same path, so - * to avoid any possible exception check - * hasPathOrNull() first. However, an exception - * for unset paths will usually be the right thing (because a - * reference.conf should exist that has the path - * set, the path should never be unset unless something is - * broken). - * - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * the path expression - * @return true if the value exists and is null, false if it - * exists and is not null - * @throws com.gu.typesafe.config.ConfigException.BadPath - * if the path expression is invalid - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is not set at all - */ - boolean getIsNull(String path); - - /** - * - * @param path - * path expression - * @return the boolean value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to boolean - */ - boolean getBoolean(String path); - - /** - * @param path - * path expression - * @return the numeric value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a number - */ - Number getNumber(String path); - - /** - * Gets the integer at the given path. If the value at the - * path has a fractional (floating point) component, it - * will be discarded and only the integer part will be - * returned (it works like a "narrowing primitive conversion" - * in the Java language specification). - * - * @param path - * path expression - * @return the 32-bit integer value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to an int (for example it is out - * of range, or it's a boolean value) - */ - int getInt(String path); - - /** - * Gets the long integer at the given path. If the value at - * the path has a fractional (floating point) component, it - * will be discarded and only the integer part will be - * returned (it works like a "narrowing primitive conversion" - * in the Java language specification). - * - * @param path - * path expression - * @return the 64-bit long value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a long - */ - long getLong(String path); - - /** - * @param path - * path expression - * @return the floating-point value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a double - */ - double getDouble(String path); - - /** - * @param path - * path expression - * @return the string value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a string - */ - String getString(String path); - - /** - * @param enumClass - * an enum class - * @param - * a generic denoting a specific type of enum - * @param path - * path expression - * @return the {@code Enum} value at the requested path - * of the requested enum class - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to an Enum - */ - public > T getEnum(Class enumClass, String path); - - /** - * @param path - * path expression - * @return the {@link com.gu.typesafe.config.ConfigObject} value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to an object - */ - com.gu.typesafe.config.ConfigObject getObject(String path); - - /** - * @param path - * path expression - * @return the nested {@code Config} value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a Config - */ - Config getConfig(String path); - - /** - * Gets the value at the path as an unwrapped Java boxed value ( - * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and - * so on - see {@link com.gu.typesafe.config.ConfigValue#unwrapped()}). - * - * @param path - * path expression - * @return the unwrapped value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - */ - Object getAnyRef(String path); - - /** - * Gets the value at the given path, unless the value is a - * null value or missing, in which case it throws just like - * the other getters. Use {@code get()} on the {@link - * Config#root()} object (or other object in the tree) if you - * want an unprocessed value. - * - * @param path - * path expression - * @return the value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - */ - com.gu.typesafe.config.ConfigValue getValue(String path); - - /** - * Gets a value as a size in bytes (parses special strings like "128M"). If - * the value is already a number, then it's left alone; if it's a string, - * it's parsed understanding unit suffixes such as "128K", as documented in - * the the - * spec. - * - * @param path - * path expression - * @return the value at the requested path, in bytes - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a size in bytes - */ - Long getBytes(String path); - - /** - * Gets a value as an amount of memory (parses special strings like "128M"). If - * the value is already a number, then it's left alone; if it's a string, - * it's parsed understanding unit suffixes such as "128K", as documented in - * the the - * spec. - * - * @since 1.3.0 - * - * @param path - * path expression - * @return the value at the requested path, in bytes - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a size in bytes - */ - com.gu.typesafe.config.ConfigMemorySize getMemorySize(String path); - - /** - * Get value as a duration in milliseconds. If the value is already a - * number, then it's left alone; if it's a string, it's parsed understanding - * units suffixes like "10m" or "5ns" as documented in the the - * spec. - * - * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} - * - * @param path - * path expression - * @return the duration value at the requested path, in milliseconds - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a number of milliseconds - */ - @Deprecated Long getMilliseconds(String path); - - /** - * Get value as a duration in nanoseconds. If the value is already a number - * it's taken as milliseconds and converted to nanoseconds. If it's a - * string, it's parsed understanding unit suffixes, as for - * {@link #getDuration(String, TimeUnit)}. - * - * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} - * - * @param path - * path expression - * @return the duration value at the requested path, in nanoseconds - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a number of nanoseconds - */ - @Deprecated Long getNanoseconds(String path); - - /** - * Gets a value as a duration in a specified - * {@link java.util.concurrent.TimeUnit TimeUnit}. If the value is already a - * number, then it's taken as milliseconds and then converted to the - * requested TimeUnit; if it's a string, it's parsed understanding units - * suffixes like "10m" or "5ns" as documented in the the - * spec. - * - * @since 1.2.0 - * - * @param path - * path expression - * @param unit - * convert the return value to this time unit - * @return the duration value at the requested path, in the given TimeUnit - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a number of the given TimeUnit - */ - long getDuration(String path, TimeUnit unit); - - /** - * Gets a value as a java.time.Duration. If the value is - * already a number, then it's taken as milliseconds; if it's - * a string, it's parsed understanding units suffixes like - * "10m" or "5ns" as documented in the the - * spec. This method never returns null. - * - * @since 1.3.0 - * - * @param path - * path expression - * @return the duration value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a number of the given TimeUnit - */ - Duration getDuration(String path); - - /** - * Gets a value as a java.time.Period. If the value is - * already a number, then it's taken as days; if it's - * a string, it's parsed understanding units suffixes like - * "10d" or "5w" as documented in the the - * spec. This method never returns null. - * - * @since 1.3.0 - * - * @param path - * path expression - * @return the period value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a number of the given TimeUnit - */ - Period getPeriod(String path); - - /** - * Gets a value as a java.time.temporal.TemporalAmount. - * This method will first try get get the value as a java.time.Duration, and if unsuccessful, - * then as a java.time.Period. - * This means that values like "5m" will be parsed as 5 minutes rather than 5 months - * @param path path expression - * @return the temporal value at the requested path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to Long or String - * @throws com.gu.typesafe.config.ConfigException.BadValue - * if value cannot be parsed as a TemporalAmount - */ - TemporalAmount getTemporal(String path); - - /** - * Gets a list value (with any element type) as a {@link com.gu.typesafe.config.ConfigList}, which - * implements {@code java.util.List}. Throws if the path is - * unset or null. - * - * @param path - * the path to the list value. - * @return the {@link com.gu.typesafe.config.ConfigList} at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a ConfigList - */ - ConfigList getList(String path); - - /** - * Gets a list value with boolean elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to boolean. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of booleans - */ - List getBooleanList(String path); - - /** - * Gets a list value with number elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to number. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of numbers - */ - List getNumberList(String path); - - /** - * Gets a list value with int elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to int. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of ints - */ - List getIntList(String path); - - /** - * Gets a list value with long elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to long. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of longs - */ - List getLongList(String path); - - /** - * Gets a list value with double elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to double. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of doubles - */ - List getDoubleList(String path); - - /** - * Gets a list value with string elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to string. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of strings - */ - List getStringList(String path); - - /** - * Gets a list value with {@code Enum} elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to {@code Enum}. - * - * @param enumClass - * the enum class - * @param - * a generic denoting a specific type of enum - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of {@code Enum} - */ - > List getEnumList(Class enumClass, String path); - - /** - * Gets a list value with object elements. Throws if the - * path is unset or null or not a list or contains values not - * convertible to ConfigObject. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of objects - */ - List getObjectList(String path); - - /** - * Gets a list value with Config elements. - * Throws if the path is unset or null or not a list or - * contains values not convertible to Config. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of configs - */ - List getConfigList(String path); - - /** - * Gets a list value with any kind of elements. Throws if the - * path is unset or null or not a list. Each element is - * "unwrapped" (see {@link com.gu.typesafe.config.ConfigValue#unwrapped()}). - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list - */ - List getAnyRefList(String path); - - /** - * Gets a list value with elements representing a size in - * bytes. Throws if the path is unset or null or not a list - * or contains values not convertible to memory sizes. - * - * @param path - * the path to the list value. - * @return the list at the path - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws com.gu.typesafe.config.ConfigException.WrongType - * if value is not convertible to a list of memory sizes - */ - List getBytesList(String path); - - /** - * Gets a list, converting each value in the list to a memory size, using the - * same rules as {@link #getMemorySize(String)}. - * - * @since 1.3.0 - * @param path - * a path expression - * @return list of memory sizes - * @throws com.gu.typesafe.config.ConfigException.Missing - * if value is absent or null - * @throws ConfigException.WrongType - * if value is not convertible to a list of memory sizes - */ - List getMemorySizeList(String path); - - /** - * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} - * @param path the path - * @return list of millisecond values - */ - @Deprecated List getMillisecondsList(String path); - - /** - * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} - * @param path the path - * @return list of nanosecond values - */ - @Deprecated List getNanosecondsList(String path); - - /** - * Gets a list, converting each value in the list to a duration, using the - * same rules as {@link #getDuration(String, TimeUnit)}. - * - * @since 1.2.0 - * @param path - * a path expression - * @param unit - * time units of the returned values - * @return list of durations, in the requested units - */ - List getDurationList(String path, TimeUnit unit); - - /** - * Gets a list, converting each value in the list to a duration, using the - * same rules as {@link #getDuration(String)}. - * - * @since 1.3.0 - * @param path - * a path expression - * @return list of durations - */ - List getDurationList(String path); - - /** - * Clone the config with only the given path (and its children) retained; - * all sibling paths are removed. - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * path to keep - * @return a copy of the config minus all paths except the one specified - */ - Config withOnlyPath(String path); - - /** - * Clone the config with the given path removed. - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * path expression to remove - * @return a copy of the config minus the specified path - */ - Config withoutPath(String path); - - /** - * Places the config inside another {@code Config} at the given path. - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param path - * path expression to store this config at. - * @return a {@code Config} instance containing this config at the given - * path. - */ - Config atPath(String path); - - /** - * Places the config inside a {@code Config} at the given key. See also - * atPath(). Note that a key is NOT a path expression (see - * {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link com.gu.typesafe.config.ConfigUtil#splitPath}). - * - * @param key - * key to store this config at. - * @return a {@code Config} instance containing this config at the given - * key. - */ - Config atKey(String key); - - /** - * Returns a {@code Config} based on this one, but with the given path set - * to the given value. Does not modify this instance (since it's immutable). - * If the path already has a value, that value is replaced. To remove a - * value, use withoutPath(). - *

- * Note that path expressions have a syntax and sometimes require quoting - * (see {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). - * - * @param path - * path expression for the value's new location - * @param value - * value at the new path - * @return the new instance with the new map entry - */ - Config withValue(String path, ConfigValue value); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigBeanFactory.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigBeanFactory.java deleted file mode 100644 index 6c7167bf1ce..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigBeanFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.gu.typesafe.config; - -import com.gu.typesafe.config.impl.ConfigBeanImpl; - -/** - * Factory for automatically creating a Java class from a {@link Config}. - * See {@link #create(Config,Class)}. - * - * @since 1.3.0 - */ -public class ConfigBeanFactory { - - /** - * Creates an instance of a class, initializing its fields from a {@link Config}. - * - * Example usage: - * - *

-     * Config configSource = ConfigFactory.load().getConfig("foo");
-     * FooConfig config = ConfigBeanFactory.create(configSource, FooConfig.class);
-     * 
- * - * The Java class should follow JavaBean conventions. Field types - * can be any of the types you can normally get from a {@link - * Config}, including java.time.Duration or {@link - * ConfigMemorySize}. Fields may also be another JavaBean-style - * class. - * - * Fields are mapped to config by converting the config key to - * camel case. So the key foo-bar becomes JavaBean - * setter setFooBar. - * - * @since 1.3.0 - * - * @param config source of config information - * @param clazz class to be instantiated - * @param the type of the class to be instantiated - * @return an instance of the class populated with data from the config - * @throws ConfigException.BadBean - * If something is wrong with the JavaBean - * @throws ConfigException.ValidationFailed - * If the config doesn't conform to the bean's implied schema - * @throws ConfigException - * Can throw the same exceptions as the getters on Config - */ - public static T create(Config config, Class clazz) { - return ConfigBeanImpl.createInternal(config, clazz); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigException.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigException.java deleted file mode 100644 index 57246930e20..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigException.java +++ /dev/null @@ -1,460 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.io.IOException; -import java.io.Serializable; -import java.lang.reflect.Field; - -import com.gu.typesafe.config.impl.ConfigImplUtil; - -/** - * All exceptions thrown by the library are subclasses of - * ConfigException. - */ -public abstract class ConfigException extends RuntimeException implements Serializable { - private static final long serialVersionUID = 1L; - - final private transient ConfigOrigin origin; - - protected ConfigException(ConfigOrigin origin, String message, - Throwable cause) { - super(origin.description() + ": " + message, cause); - this.origin = origin; - } - - protected ConfigException(ConfigOrigin origin, String message) { - this(origin.description() + ": " + message, null); - } - - protected ConfigException(String message, Throwable cause) { - super(message, cause); - this.origin = null; - } - - protected ConfigException(String message) { - this(message, null); - } - - /** - * Returns an "origin" (such as a filename and line number) for the - * exception, or null if none is available. If there's no sensible origin - * for a given exception, or the kind of exception doesn't meaningfully - * relate to a particular origin file, this returns null. Never assume this - * will return non-null, it can always return null. - * - * @return origin of the problem, or null if unknown/inapplicable - */ - public ConfigOrigin origin() { - return origin; - } - - // we customize serialization because ConfigOrigin isn't - // serializable and we don't want it to be (don't want to - // support it) - private void writeObject(java.io.ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - ConfigImplUtil.writeOrigin(out, origin); - } - - // For deserialization - uses reflection to set the final origin field on the object - private static void setOriginField(T hasOriginField, Class clazz, - ConfigOrigin origin) throws IOException { - // circumvent "final" - Field f; - try { - f = clazz.getDeclaredField("origin"); - } catch (NoSuchFieldException e) { - throw new IOException(clazz.getSimpleName() + " has no origin field?", e); - } catch (SecurityException e) { - throw new IOException("unable to fill out origin field in " + - clazz.getSimpleName(), e); - } - f.setAccessible(true); - try { - f.set(hasOriginField, origin); - } catch (IllegalArgumentException e) { - throw new IOException("unable to set origin field", e); - } catch (IllegalAccessException e) { - throw new IOException("unable to set origin field", e); - } - } - - private void readObject(java.io.ObjectInputStream in) throws IOException, - ClassNotFoundException { - in.defaultReadObject(); - ConfigOrigin origin = ConfigImplUtil.readOrigin(in); - setOriginField(this, ConfigException.class, origin); - } - - /** - * Exception indicating that the type of a value does not match the type you - * requested. - * - */ - public static class WrongType extends ConfigException { - private static final long serialVersionUID = 1L; - - public WrongType(ConfigOrigin origin, String path, String expected, String actual, - Throwable cause) { - super(origin, path + " has type " + actual + " rather than " + expected, cause); - } - - public WrongType(ConfigOrigin origin, String path, String expected, String actual) { - this(origin, path, expected, actual, null); - } - - public WrongType(ConfigOrigin origin, String message, Throwable cause) { - super(origin, message, cause); - } - - public WrongType(ConfigOrigin origin, String message) { - super(origin, message, null); - } - } - - /** - * Exception indicates that the setting was never set to anything, not even - * null. - */ - public static class Missing extends ConfigException { - private static final long serialVersionUID = 1L; - - public Missing(String path, Throwable cause) { - super("No configuration setting found for key '" + path + "'", - cause); - } - - public Missing(ConfigOrigin origin, String path) { - this(origin, "No configuration setting found for key '" + path + "'", null); - } - - public Missing(String path) { - this(path, null); - } - - protected Missing(ConfigOrigin origin, String message, Throwable cause) { - super(origin, message, cause); - } - - } - - /** - * Exception indicates that the setting was treated as missing because it - * was set to null. - */ - public static class Null extends Missing { - private static final long serialVersionUID = 1L; - - private static String makeMessage(String path, String expected) { - if (expected != null) { - return "Configuration key '" + path - + "' is set to null but expected " + expected; - } else { - return "Configuration key '" + path + "' is null"; - } - } - - public Null(ConfigOrigin origin, String path, String expected, - Throwable cause) { - super(origin, makeMessage(path, expected), cause); - } - - public Null(ConfigOrigin origin, String path, String expected) { - this(origin, path, expected, null); - } - } - - /** - * Exception indicating that a value was messed up, for example you may have - * asked for a duration and the value can't be sensibly parsed as a - * duration. - * - */ - public static class BadValue extends ConfigException { - private static final long serialVersionUID = 1L; - - public BadValue(ConfigOrigin origin, String path, String message, - Throwable cause) { - super(origin, "Invalid value at '" + path + "': " + message, cause); - } - - public BadValue(ConfigOrigin origin, String path, String message) { - this(origin, path, message, null); - } - - public BadValue(String path, String message, Throwable cause) { - super("Invalid value at '" + path + "': " + message, cause); - } - - public BadValue(String path, String message) { - this(path, message, null); - } - } - - /** - * Exception indicating that a path expression was invalid. Try putting - * double quotes around path elements that contain "special" characters. - * - */ - public static class BadPath extends ConfigException { - private static final long serialVersionUID = 1L; - - public BadPath(ConfigOrigin origin, String path, String message, - Throwable cause) { - super(origin, - path != null ? ("Invalid path '" + path + "': " + message) - : message, cause); - } - - public BadPath(ConfigOrigin origin, String path, String message) { - this(origin, path, message, null); - } - - public BadPath(String path, String message, Throwable cause) { - super(path != null ? ("Invalid path '" + path + "': " + message) - : message, cause); - } - - public BadPath(String path, String message) { - this(path, message, null); - } - - public BadPath(ConfigOrigin origin, String message) { - this(origin, null, message); - } - } - - /** - * Exception indicating that there's a bug in something (possibly the - * library itself) or the runtime environment is broken. This exception - * should never be handled; instead, something should be fixed to keep the - * exception from occurring. This exception can be thrown by any method in - * the library. - */ - public static class BugOrBroken extends ConfigException { - private static final long serialVersionUID = 1L; - - public BugOrBroken(String message, Throwable cause) { - super(message, cause); - } - - public BugOrBroken(String message) { - this(message, null); - } - } - - /** - * Exception indicating that there was an IO error. - * - */ - public static class IO extends ConfigException { - private static final long serialVersionUID = 1L; - - public IO(ConfigOrigin origin, String message, Throwable cause) { - super(origin, message, cause); - } - - public IO(ConfigOrigin origin, String message) { - this(origin, message, null); - } - } - - /** - * Exception indicating that there was a parse error. - * - */ - public static class Parse extends ConfigException { - private static final long serialVersionUID = 1L; - - public Parse(ConfigOrigin origin, String message, Throwable cause) { - super(origin, message, cause); - } - - public Parse(ConfigOrigin origin, String message) { - this(origin, message, null); - } - } - - /** - * Exception indicating that a substitution did not resolve to anything. - * Thrown by {@link Config#resolve}. - */ - public static class UnresolvedSubstitution extends Parse { - private static final long serialVersionUID = 1L; - - private final String detail; - - public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) { - super(origin, "Could not resolve substitution to a value: " + detail, cause); - this.detail = detail; - } - - public UnresolvedSubstitution(ConfigOrigin origin, String detail) { - this(origin, detail, null); - } - - private UnresolvedSubstitution(UnresolvedSubstitution wrapped, ConfigOrigin origin, String message) { - super(origin, message, wrapped); - this.detail = wrapped.detail; - } - - public UnresolvedSubstitution addExtraDetail(String extra) { - return new UnresolvedSubstitution(this, origin(), String.format(extra, detail)); - } - } - - /** - * Exception indicating that you tried to use a function that requires - * substitutions to be resolved, but substitutions have not been resolved - * (that is, {@link Config#resolve} was not called). This is always a bug in - * either application code or the library; it's wrong to write a handler for - * this exception because you should be able to fix the code to avoid it by - * adding calls to {@link Config#resolve}. - */ - public static class NotResolved extends BugOrBroken { - private static final long serialVersionUID = 1L; - - public NotResolved(String message, Throwable cause) { - super(message, cause); - } - - public NotResolved(String message) { - this(message, null); - } - } - - /** - * Information about a problem that occurred in {@link Config#checkValid}. A - * {@link ConfigException.ValidationFailed} exception thrown from - * checkValid() includes a list of problems encountered. - */ - public static class ValidationProblem implements Serializable { - - final private String path; - final private transient ConfigOrigin origin; - final private String problem; - - public ValidationProblem(String path, ConfigOrigin origin, String problem) { - this.path = path; - this.origin = origin; - this.problem = problem; - } - - /** - * Returns the config setting causing the problem. - * @return the path of the problem setting - */ - public String path() { - return path; - } - - /** - * Returns where the problem occurred (origin may include info on the - * file, line number, etc.). - * @return the origin of the problem setting - */ - public ConfigOrigin origin() { - return origin; - } - - /** - * Returns a description of the problem. - * @return description of the problem - */ - public String problem() { - return problem; - } - - // We customize serialization because ConfigOrigin isn't - // serializable and we don't want it to be - private void writeObject(java.io.ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - ConfigImplUtil.writeOrigin(out, origin); - } - - private void readObject(java.io.ObjectInputStream in) throws IOException, - ClassNotFoundException { - in.defaultReadObject(); - ConfigOrigin origin = ConfigImplUtil.readOrigin(in); - setOriginField(this, ValidationProblem.class, origin); - } - - @Override - public String toString() { - return "ValidationProblem(" + path + "," + origin + "," + problem + ")"; - } - } - - /** - * Exception indicating that {@link Config#checkValid} found validity - * problems. The problems are available via the {@link #problems()} method. - * The getMessage() of this exception is a potentially very - * long string listing all the problems found. - */ - public static class ValidationFailed extends ConfigException { - private static final long serialVersionUID = 1L; - - final private Iterable problems; - - public ValidationFailed(Iterable problems) { - super(makeMessage(problems), null); - this.problems = problems; - } - - public Iterable problems() { - return problems; - } - - private static String makeMessage(Iterable problems) { - StringBuilder sb = new StringBuilder(); - for (ValidationProblem p : problems) { - sb.append(p.origin().description()); - sb.append(": "); - sb.append(p.path()); - sb.append(": "); - sb.append(p.problem()); - sb.append(", "); - } - if (sb.length() == 0) - throw new ConfigException.BugOrBroken( - "ValidationFailed must have a non-empty list of problems"); - sb.setLength(sb.length() - 2); // chop comma and space - - return sb.toString(); - } - } - - /** - * Some problem with a JavaBean we are trying to initialize. - * @since 1.3.0 - */ - public static class BadBean extends BugOrBroken { - private static final long serialVersionUID = 1L; - - public BadBean(String message, Throwable cause) { - super(message, cause); - } - - public BadBean(String message) { - this(message, null); - } - } - - /** - * Exception that doesn't fall into any other category. - */ - public static class Generic extends ConfigException { - private static final long serialVersionUID = 1L; - - public Generic(String message, Throwable cause) { - super(message, cause); - } - - public Generic(String message) { - this(message, null); - } - } - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigFactory.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigFactory.java deleted file mode 100644 index 093170f284f..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigFactory.java +++ /dev/null @@ -1,1258 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import com.gu.typesafe.config.impl.ConfigImpl; -import com.gu.typesafe.config.impl.Parseable; - -import java.io.File; -import java.io.Reader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; -import java.util.Optional; -import java.util.Properties; -import java.util.concurrent.Callable; - -/** - * Contains static methods for creating {@link Config} instances. - * - *

- * See also {@link com.gu.typesafe.config.ConfigValueFactory} which contains static methods for - * converting Java values into a {@link ConfigObject}. You can then convert a - * {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}. - * - *

- * The static methods with "load" in the name do some sort of higher-level - * operation potentially parsing multiple resources and resolving substitutions, - * while the ones with "parse" in the name just create a {@link ConfigValue} - * from a resource and nothing else. - * - *

You can find an example app and library on - * GitHub. Also be sure to read the package - * overview which describes the big picture as shown in those - * examples. - */ -public final class ConfigFactory { - private static final String STRATEGY_PROPERTY_NAME = "config.strategy"; - private static final String OVERRIDE_WITH_ENV_PROPERTY_NAME = "config.override_with_env_vars"; - - private ConfigFactory() { - } - - /** - * Loads an application's configuration from the given classpath resource or - * classpath resource basename, sandwiches it between default reference - * config and default overrides, and then resolves it. The classpath - * resource is "raw" (it should have no "/" prefix, and is not made relative - * to any package, so it's like {@link ClassLoader#getResource} not - * {@link Class#getResource}). - * - *

- * Resources are loaded from the current thread's - * {@link Thread#getContextClassLoader()}. In general, a library needs its - * configuration to come from the class loader used to load that library, so - * the proper "reference.conf" are present. - * - *

- * The loaded object will already be resolved (substitutions have already - * been processed). As a result, if you add more fallbacks then they won't - * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If - * you want to parse additional files or something then you need to use - * {@link #load(Config)}. - * - *

- * To load a standalone resource (without the default reference and default - * overrides), use {@link #parseResourcesAnySyntax(String)} rather than this - * method. To load only the reference config use {@link #defaultReference()} - * and to load only the overrides use {@link #defaultOverrides()}. - * - * @param resourceBasename - * name (optionally without extension) of a resource on classpath - * @return configuration for an application relative to context class loader - */ - public static Config load(String resourceBasename) { - return load(resourceBasename, ConfigParseOptions.defaults(), - com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - /** - * Like {@link #load(String)} but uses the supplied class loader instead of - * the current thread's context class loader. - * - *

- * To load a standalone resource (without the default reference and default - * overrides), use {@link #parseResourcesAnySyntax(ClassLoader, String)} - * rather than this method. To load only the reference config use - * {@link #defaultReference(ClassLoader)} and to load only the overrides use - * {@link #defaultOverrides(ClassLoader)}. - * - * @param loader class loader to look for resources in - * @param resourceBasename basename (no .conf/.json/.properties suffix) - * @return configuration for an application relative to given class loader - */ - public static Config load(ClassLoader loader, String resourceBasename) { - return load(resourceBasename, ConfigParseOptions.defaults().setClassLoader(loader), - com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - /** - * Like {@link #load(String)} but allows you to specify parse and resolve - * options. - * - * @param resourceBasename - * the classpath resource name with optional extension - * @param parseOptions - * options to use when parsing the resource - * @param resolveOptions - * options to use when resolving the stack - * @return configuration for an application - */ - public static Config load(String resourceBasename, ConfigParseOptions parseOptions, - com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); - Config appConfig = ConfigFactory.parseResourcesAnySyntax(resourceBasename, withLoader); - return load(withLoader.getClassLoader(), appConfig, resolveOptions); - } - - /** - * Like {@link #load(String,ConfigParseOptions, com.gu.typesafe.config.ConfigResolveOptions)} but - * has a class loader parameter that overrides any from the - * {@code ConfigParseOptions}. - * - * @param loader - * class loader in which to find resources (overrides loader in - * parse options) - * @param resourceBasename - * the classpath resource name with optional extension - * @param parseOptions - * options to use when parsing the resource (class loader - * overridden) - * @param resolveOptions - * options to use when resolving the stack - * @return configuration for an application - */ - public static Config load(ClassLoader loader, String resourceBasename, - ConfigParseOptions parseOptions, com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - return load(resourceBasename, parseOptions.setClassLoader(loader), resolveOptions); - } - - private static ClassLoader checkedContextClassLoader(String methodName) { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - if (loader == null) - throw new ConfigException.BugOrBroken("Context class loader is not set for the current thread; " - + "if Thread.currentThread().getContextClassLoader() returns null, you must pass a ClassLoader " - + "explicitly to ConfigFactory." + methodName); - else - return loader; - } - - private static ConfigParseOptions ensureClassLoader(ConfigParseOptions options, String methodName) { - if (options.getClassLoader() == null) - return options.setClassLoader(checkedContextClassLoader(methodName)); - else - return options; - } - - /** - * Assembles a standard configuration using a custom Config - * object rather than loading "application.conf". The Config - * object will be sandwiched between the default reference config and - * default overrides and then resolved. - * - * @param config - * the application's portion of the configuration - * @return resolved configuration with overrides and fallbacks added - */ - public static Config load(Config config) { - return load(checkedContextClassLoader("load"), config); - } - - /** - * Like {@link #load(Config)} but allows you to specify - * the class loader for looking up resources. - * - * @param loader - * the class loader to use to find resources - * @param config - * the application's portion of the configuration - * @return resolved configuration with overrides and fallbacks added - */ - public static Config load(ClassLoader loader, Config config) { - return load(loader, config, com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - /** - * Like {@link #load(Config)} but allows you to specify - * {@link com.gu.typesafe.config.ConfigResolveOptions}. - * - * @param config - * the application's portion of the configuration - * @param resolveOptions - * options for resolving the assembled config stack - * @return resolved configuration with overrides and fallbacks added - */ - public static Config load(Config config, com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - return load(checkedContextClassLoader("load"), config, resolveOptions); - } - - /** - * Like {@link #load(Config, com.gu.typesafe.config.ConfigResolveOptions)} but allows you to specify - * a class loader other than the context class loader. - * - * @param loader - * class loader to use when looking up override and reference - * configs - * @param config - * the application's portion of the configuration - * @param resolveOptions - * options for resolving the assembled config stack - * @return resolved configuration with overrides and fallbacks added - */ - public static Config load(ClassLoader loader, Config config, com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - return defaultOverrides(loader).withFallback(config) - .withFallback(ConfigImpl.defaultReferenceUnresolved(loader)) - .resolve(resolveOptions); - } - - - - /** - * Loads a default configuration, equivalent to {@link #load(Config) - * load(defaultApplication())} in most cases. This configuration should be used by - * libraries and frameworks unless an application provides a different one. - *

- * This method may return a cached singleton so will not see changes to - * system properties or config files. (Use {@link #invalidateCaches()} to - * force it to reload.) - * - * @return configuration for an application - */ - public static Config load() { - ClassLoader loader = checkedContextClassLoader("load"); - return load(loader); - } - - /** - * Like {@link #load()} but allows specifying parse options. - * - * @param parseOptions - * Options for parsing resources - * @return configuration for an application - */ - public static Config load(ConfigParseOptions parseOptions) { - return load(parseOptions, com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - /** - * Like {@link #load()} but allows specifying a class loader other than the - * thread's current context class loader. - * - * @param loader - * class loader for finding resources - * @return configuration for an application - */ - public static Config load(final ClassLoader loader) { - final ConfigParseOptions withLoader = ConfigParseOptions.defaults().setClassLoader(loader); - return ConfigImpl.computeCachedConfig(loader, "load", new Callable() { - @Override - public Config call() { - return load(loader, defaultApplication(withLoader)); - } - }); - } - - /** - * Like {@link #load()} but allows specifying a class loader other than the - * thread's current context class loader and also specify parse options. - * - * @param loader - * class loader for finding resources (overrides any loader in parseOptions) - * @param parseOptions - * Options for parsing resources - * @return configuration for an application - */ - public static Config load(ClassLoader loader, ConfigParseOptions parseOptions) { - return load(parseOptions.setClassLoader(loader)); - } - - /** - * Like {@link #load()} but allows specifying a class loader other than the - * thread's current context class loader and also specify resolve options. - * - * @param loader - * class loader for finding resources - * @param resolveOptions - * options for resolving the assembled config stack - * @return configuration for an application - */ - public static Config load(ClassLoader loader, com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - return load(loader, ConfigParseOptions.defaults(), resolveOptions); - } - - - /** - * Like {@link #load()} but allows specifying a class loader other than the - * thread's current context class loader, parse options, and resolve options. - * - * @param loader - * class loader for finding resources (overrides any loader in parseOptions) - * @param parseOptions - * Options for parsing resources - * @param resolveOptions - * options for resolving the assembled config stack - * @return configuration for an application - */ - public static Config load(ClassLoader loader, ConfigParseOptions parseOptions, com.gu.typesafe.config.ConfigResolveOptions resolveOptions) { - final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); - return load(loader, defaultApplication(withLoader), resolveOptions); - } - - /** - * Like {@link #load()} but allows specifying parse options and resolve - * options. - * - * @param parseOptions - * Options for parsing resources - * @param resolveOptions - * options for resolving the assembled config stack - * @return configuration for an application - * - * @since 1.3.0 - */ - public static Config load(ConfigParseOptions parseOptions, final ConfigResolveOptions resolveOptions) { - final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); - return load(defaultApplication(withLoader), resolveOptions); - } - - /** - * Obtains the default reference configuration, which is currently created - * by merging all resources "reference.conf" found on the classpath and - * overriding the result with system properties. The returned reference - * configuration will already have substitutions resolved. - * - *

- * Libraries and frameworks should ship with a "reference.conf" in their - * jar. - * - *

- * The reference config must be looked up in the class loader that contains - * the libraries that you want to use with this config, so the - * "reference.conf" for each library can be found. Use - * {@link #defaultReference(ClassLoader)} if the context class loader is not - * suitable. - * - *

- * The {@link #load()} methods merge this configuration for you - * automatically. - * - *

- * Future versions may look for reference configuration in more places. It - * is not guaranteed that this method only looks at - * "reference.conf". - * - * @return the default reference config for context class loader - */ - public static Config defaultReference() { - return defaultReference(checkedContextClassLoader("defaultReference")); - } - - /** - * Like {@link #defaultReference()} but allows you to specify a class loader - * to use rather than the current context class loader. - * - * @param loader class loader to look for resources in - * @return the default reference config for this class loader - */ - public static Config defaultReference(ClassLoader loader) { - return ConfigImpl.defaultReference(loader); - } - - /** - * Obtains the default reference configuration, which is currently created - * by merging all resources "reference.conf" found on the classpath and - * overriding the result with system properties. - * - *

- * While the returned reference configuration is guaranteed to be - * resolvable (that is, there will be no substitutions that cannot be - * resolved), it is returned in an unresolved state for the purpose of - * allowing substitutions to be overridden by a config layer that falls - * back to this one. - * - *

- * Libraries and frameworks should ship with a "reference.conf" in their - * jar. - * - *

- * The reference config must be looked up in the class loader that contains - * the libraries that you want to use with this config, so the - * "reference.conf" for each library can be found. Use - * {@link #defaultReference(ClassLoader)} if the context class loader is not - * suitable. - * - *

- * The {@link #load()} methods merge this configuration for you - * automatically. - * - *

- * Future versions may look for reference configuration in more places. It - * is not guaranteed that this method only looks at - * "reference.conf". - * - * @return the unresolved default reference config for the context class - * loader - */ - public static Config defaultReferenceUnresolved() { - return defaultReferenceUnresolved(checkedContextClassLoader("defaultReferenceUnresolved")); - } - - /** - * Like {@link #defaultReferenceUnresolved()} but allows you to specify a - * class loader to use rather than the current context class loader. - * - * @param loader class loader to look for resources in - * @return the unresolved default reference config for this class loader - */ - public static Config defaultReferenceUnresolved(ClassLoader loader) { - return ConfigImpl.defaultReferenceUnresolved(loader); - } - - /** - * Obtains the default override configuration, which currently consists of - * system properties. The returned override configuration will already have - * substitutions resolved. - * - *

- * The {@link #load()} methods merge this configuration for you - * automatically. - * - *

- * Future versions may get overrides in more places. It is not guaranteed - * that this method only uses system properties. - * - * @return the default override configuration - */ - public static Config defaultOverrides() { - if (getOverrideWithEnv()) { - return systemEnvironmentOverrides().withFallback(systemProperties()); - } else { - return systemProperties(); - } - } - - /** - * Like {@link #defaultOverrides()} but allows you to specify a class loader - * to use rather than the current context class loader. - * - * @param loader class loader to look for resources in - * @return the default override configuration - */ - public static Config defaultOverrides(ClassLoader loader) { - return defaultOverrides(); - } - - /** - * Obtains the default application-specific configuration, - * which defaults to parsing application.conf, - * application.json, and - * application.properties on the classpath, but - * can also be rerouted using the config.file, - * config.resource, and config.url - * system properties. - * - *

The no-arguments {@link #load()} method automatically - * stacks the {@link #defaultReference()}, {@link - * #defaultApplication()}, and {@link #defaultOverrides()} - * configs. You would use defaultApplication() - * directly only if you're somehow customizing behavior by - * reimplementing load(). - * - *

The configuration returned by - * defaultApplication() will not be resolved - * already, in contrast to defaultReference() and - * defaultOverrides(). This is because - * application.conf would normally be resolved after - * merging with the reference and override configs. - * - *

- * If the system properties config.resource, - * config.file, or config.url are set, then the - * classpath resource, file, or URL specified in those properties will be - * used rather than the default - * application.{conf,json,properties} classpath resources. - * These system properties should not be set in code (after all, you can - * just parse whatever you want manually and then use {@link #load(Config)} - * if you don't want to use application.conf). The properties - * are intended for use by the person or script launching the application. - * For example someone might have a production.conf that - * include application.conf but then change a couple of values. - * When launching the app they could specify - * -Dconfig.resource=production.conf to get production mode. - * - *

- * If no system properties are set to change the location of the default - * configuration, defaultApplication() is equivalent to - * ConfigFactory.parseResources("application"). - * - * @since 1.3.0 - * - * @return the default application.conf or system-property-configured configuration - */ - public static Config defaultApplication() { - return defaultApplication(ConfigParseOptions.defaults()); - } - - /** - * Like {@link #defaultApplication()} but allows you to specify a class loader - * to use rather than the current context class loader. - * - * @since 1.3.0 - * - * @param loader class loader to look for resources in - * @return the default application configuration - */ - public static Config defaultApplication(ClassLoader loader) { - return defaultApplication(ConfigParseOptions.defaults().setClassLoader(loader)); - } - - /** - * Like {@link #defaultApplication()} but allows you to specify parse options. - * - * @since 1.3.0 - * - * @param options the options - * @return the default application configuration - */ - public static Config defaultApplication(ConfigParseOptions options) { - return getConfigLoadingStrategy().parseApplicationConfig(ensureClassLoader(options, "defaultApplication")); - } - - /** - * Reloads any cached configs, picking up changes to system properties for - * example. Because a {@link Config} is immutable, anyone with a reference - * to the old configs will still have the same outdated objects. However, - * new calls to {@link #load()} or {@link #defaultOverrides()} or - * {@link #defaultReference} may return a new object. - *

- * This method is primarily intended for use in unit tests, for example, - * that may want to update a system property then confirm that it's used - * correctly. In many cases, use of this method may indicate there's a - * better way to set up your code. - *

- * Caches may be reloaded immediately or lazily; once you call this method, - * the reload can occur at any time, even during the invalidation process. - * So FIRST make the changes you'd like the caches to notice, then SECOND - * call this method to invalidate caches. Don't expect that invalidating, - * making changes, then calling {@link #load()}, will work. Make changes - * before you invalidate. - */ - public static void invalidateCaches() { - // We rely on this having the side effect that it drops - // all caches - ConfigImpl.reloadSystemPropertiesConfig(); - ConfigImpl.reloadEnvVariablesConfig(); - ConfigImpl.reloadEnvVariablesOverridesConfig(); - } - - /** - * Gets an empty configuration. See also {@link #empty(String)} to create an - * empty configuration with a description, which may improve user-visible - * error messages. - * - * @return an empty configuration - */ - public static Config empty() { - return empty(null); - } - - /** - * Gets an empty configuration with a description to be used to create a - * {@link ConfigOrigin} for this Config. The description should - * be very short and say what the configuration is, like "default settings" - * or "foo settings" or something. (Presumably you will merge some actual - * settings into this empty config using {@link Config#withFallback}, making - * the description more useful.) - * - * @param originDescription - * description of the config - * @return an empty configuration - */ - public static Config empty(String originDescription) { - return ConfigImpl.emptyConfig(originDescription); - } - - /** - * Gets a Config containing the system properties from - * {@link java.lang.System#getProperties()}, parsed and converted as with - * {@link #parseProperties}. - *

- * This method can return a global immutable singleton, so it's preferred - * over parsing system properties yourself. - *

- * {@link #load} will include the system properties as overrides already, as - * will {@link #defaultReference} and {@link #defaultOverrides}. - * - *

- * Because this returns a singleton, it will not notice changes to system - * properties made after the first time this method is called. Use - * {@link #invalidateCaches()} to force the singleton to reload if you - * modify system properties. - * - * @return system properties parsed into a Config - */ - public static Config systemProperties() { - return ConfigImpl.systemPropertiesAsConfig(); - } - - /** - * Gets a Config containing the system's environment variables - * used to override configuration keys. - * Environment variables taken in considerations are starting with - * {@code CONFIG_FORCE_} - * - *

- * Environment variables are mangled in the following way after stripping the prefix "CONFIG_FORCE_": - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Env VarConfig
_   [1 underscore]. [dot]
__  [2 underscore]- [dash]
___ [3 underscore]_ [underscore]
- * - *

- * A variable like: {@code CONFIG_FORCE_a_b__c___d} - * is translated to a config key: {@code a.b-c_d} - * - *

- * This method can return a global immutable singleton, so it's preferred - * over parsing system properties yourself. - *

- * {@link #defaultOverrides} will include the system environment variables as - * overrides if `config.override_with_env_vars` is set to `true`. - * - * @return system environment variable overrides parsed into a Config - */ - public static Config systemEnvironmentOverrides() { - return ConfigImpl.envVariablesOverridesAsConfig(); - } - - /** - * Gets a Config containing the system's environment variables. - * This method can return a global immutable singleton. - * - *

- * Environment variables are used as fallbacks when resolving substitutions - * whether or not this object is included in the config being resolved, so - * you probably don't need to use this method for most purposes. It can be a - * nicer API for accessing environment variables than raw - * {@link java.lang.System#getenv(String)} though, since you can use methods - * such as {@link Config#getInt}. - * - * @return system environment variables parsed into a Config - */ - public static Config systemEnvironment() { - return ConfigImpl.envVariablesAsConfig(); - } - - /** - * Converts a Java {@link java.util.Properties} object to a - * {@link ConfigObject} using the rules documented in the HOCON - * spec. The keys in the Properties object are split on the - * period character '.' and treated as paths. The values will all end up as - * string values. If you have both "a=foo" and "a.b=bar" in your properties - * file, so "a" is both the object containing "b" and the string "foo", then - * the string value is dropped. - * - *

- * If you want to have System.getProperties() as a - * ConfigObject, it's better to use the {@link #systemProperties()} method - * which returns a cached global singleton. - * - * @param properties - * a Java Properties object - * @param options - * the parse options - * @return the parsed configuration - */ - public static Config parseProperties(Properties properties, - ConfigParseOptions options) { - return Parseable.newProperties(properties, options).parse().toConfig(); - } - - /** - * Like {@link #parseProperties(Properties, ConfigParseOptions)} but uses default - * parse options. - * @param properties - * a Java Properties object - * @return the parsed configuration - */ - public static Config parseProperties(Properties properties) { - return parseProperties(properties, ConfigParseOptions.defaults()); - } - - /** - * Parses a Reader into a Config instance. Does not call - * {@link Config#resolve} or merge the parsed stream with any - * other configuration; this method parses a single stream and - * does nothing else. It does process "include" statements in - * the parsed stream, and may end up doing other IO due to those - * statements. - * - * @param reader - * the reader to parse - * @param options - * parse options to control how the reader is interpreted - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseReader(Reader reader, ConfigParseOptions options) { - return Parseable.newReader(reader, options).parse().toConfig(); - } - - /** - * Parses a reader into a Config instance as with - * {@link #parseReader(Reader,ConfigParseOptions)} but always uses the - * default parse options. - * - * @param reader - * the reader to parse - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseReader(Reader reader) { - return parseReader(reader, ConfigParseOptions.defaults()); - } - - /** - * Parses a URL into a Config instance. Does not call - * {@link Config#resolve} or merge the parsed stream with any - * other configuration; this method parses a single stream and - * does nothing else. It does process "include" statements in - * the parsed stream, and may end up doing other IO due to those - * statements. - * - * @param url - * the url to parse - * @param options - * parse options to control how the url is interpreted - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseURL(URL url, ConfigParseOptions options) { - return Parseable.newURL(url, options).parse().toConfig(); - } - - /** - * Parses a url into a Config instance as with - * {@link #parseURL(URL,ConfigParseOptions)} but always uses the - * default parse options. - * - * @param url - * the url to parse - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseURL(URL url) { - return parseURL(url, ConfigParseOptions.defaults()); - } - - /** - * Parses a file into a Config instance. Does not call - * {@link Config#resolve} or merge the file with any other - * configuration; this method parses a single file and does - * nothing else. It does process "include" statements in the - * parsed file, and may end up doing other IO due to those - * statements. - * - * @param file - * the file to parse - * @param options - * parse options to control how the file is interpreted - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseFile(File file, ConfigParseOptions options) { - return Parseable.newFile(file, options).parse().toConfig(); - } - - /** - * Parses a file into a Config instance as with - * {@link #parseFile(File,ConfigParseOptions)} but always uses the - * default parse options. - * - * @param file - * the file to parse - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static Config parseFile(File file) { - return parseFile(file, ConfigParseOptions.defaults()); - } - - /** - * Parses a file with a flexible extension. If the fileBasename - * already ends in a known extension, this method parses it according to - * that extension (the file's syntax must match its extension). If the - * fileBasename does not end in an extension, it parses files - * with all known extensions and merges whatever is found. - * - *

- * In the current implementation, the extension ".conf" forces - * {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and - * ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files, - * ".conf" falls back to ".json" falls back to ".properties". - * - *

- * Future versions of the implementation may add additional syntaxes or - * additional extensions. However, the ordering (fallback priority) of the - * three current extensions will remain the same. - * - *

- * If options forces a specific syntax, this method only parses - * files with an extension matching that syntax. - * - *

- * If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()} - * is true, then no files have to exist; if false, then at least one file - * has to exist. - * - * @param fileBasename - * a filename with or without extension - * @param options - * parse options - * @return the parsed configuration - */ - public static Config parseFileAnySyntax(File fileBasename, - ConfigParseOptions options) { - return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); - } - - /** - * Like {@link #parseFileAnySyntax(File,ConfigParseOptions)} but always uses - * default parse options. - * - * @param fileBasename - * a filename with or without extension - * @return the parsed configuration - */ - public static Config parseFileAnySyntax(File fileBasename) { - return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults()); - } - - /** - * Parses all resources on the classpath with the given name and merges them - * into a single Config. - * - *

- * If the resource name does not begin with a "/", it will have the supplied - * class's package added to it, in the same way as - * {@link java.lang.Class#getResource}. - * - *

- * Duplicate resources with the same name are merged such that ones returned - * earlier from {@link ClassLoader#getResources} fall back to (have higher - * priority than) the ones returned later. This implies that resources - * earlier in the classpath override those later in the classpath when they - * configure the same setting. However, in practice real applications may - * not be consistent about classpath ordering, so be careful. It may be best - * to avoid assuming too much. - * - * @param klass - * klass.getClassLoader() will be used to load - * resources, and non-absolute resource names will have this - * class's package added - * @param resource - * resource to look up, relative to klass's package - * or absolute starting with a "/" - * @param options - * parse options - * @return the parsed configuration - */ - public static Config parseResources(Class klass, String resource, - ConfigParseOptions options) { - return Parseable.newResources(klass, resource, options).parse() - .toConfig(); - } - - /** - * Like {@link #parseResources(Class,String,ConfigParseOptions)} but always uses - * default parse options. - * - * @param klass - * klass.getClassLoader() will be used to load - * resources, and non-absolute resource names will have this - * class's package added - * @param resource - * resource to look up, relative to klass's package - * or absolute starting with a "/" - * @return the parsed configuration - */ - public static Config parseResources(Class klass, String resource) { - return parseResources(klass, resource, ConfigParseOptions.defaults()); - } - - /** - * Parses classpath resources with a flexible extension. In general, this - * method has the same behavior as - * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath - * resources instead, as in {@link #parseResources}. - * - *

- * There is a thorny problem with this method, which is that - * {@link java.lang.ClassLoader#getResources} must be called separately for - * each possible extension. The implementation ends up with separate lists - * of resources called "basename.conf" and "basename.json" for example. As a - * result, the ideal ordering between two files with different extensions is - * unknown; there is no way to figure out how to merge the two lists in - * classpath order. To keep it simple, the lists are simply concatenated, - * with the same syntax priorities as - * {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()} - * - all ".conf" resources are ahead of all ".json" resources which are - * ahead of all ".properties" resources. - * - * @param klass - * class which determines the ClassLoader and the - * package for relative resource names - * @param resourceBasename - * a resource name as in {@link java.lang.Class#getResource}, - * with or without extension - * @param options - * parse options (class loader is ignored in favor of the one - * from klass) - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(Class klass, String resourceBasename, - ConfigParseOptions options) { - return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename, - options).toConfig(); - } - - /** - * Like {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} - * but always uses default parse options. - * - * @param klass - * klass.getClassLoader() will be used to load - * resources, and non-absolute resource names will have this - * class's package added - * @param resourceBasename - * a resource name as in {@link java.lang.Class#getResource}, - * with or without extension - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(Class klass, String resourceBasename) { - return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults()); - } - - /** - * Parses all resources on the classpath with the given name and merges them - * into a single Config. - * - *

- * This works like {@link java.lang.ClassLoader#getResource}, not like - * {@link java.lang.Class#getResource}, so the name never begins with a - * slash. - * - *

- * See {@link #parseResources(Class,String,ConfigParseOptions)} for full - * details. - * - * @param loader - * will be used to load resources by setting this loader on the - * provided options - * @param resource - * resource to look up - * @param options - * parse options (class loader is ignored) - * @return the parsed configuration - */ - public static Config parseResources(ClassLoader loader, String resource, - ConfigParseOptions options) { - return parseResources(resource, options.setClassLoader(loader)); - } - - /** - * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but always uses - * default parse options. - * - * @param loader - * will be used to load resources - * @param resource - * resource to look up in the loader - * @return the parsed configuration - */ - public static Config parseResources(ClassLoader loader, String resource) { - return parseResources(loader, resource, ConfigParseOptions.defaults()); - } - - /** - * Parses classpath resources with a flexible extension. In general, this - * method has the same behavior as - * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath - * resources instead, as in - * {@link #parseResources(ClassLoader,String,ConfigParseOptions)}. - * - *

- * {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} differs - * in the syntax for the resource name, but otherwise see - * {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} for - * some details and caveats on this method. - * - * @param loader - * class loader to look up resources in, will be set on options - * @param resourceBasename - * a resource name as in - * {@link java.lang.ClassLoader#getResource}, with or without - * extension - * @param options - * parse options (class loader ignored) - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename, - ConfigParseOptions options) { - return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options.setClassLoader(loader)) - .toConfig(); - } - - /** - * Like {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} but always uses - * default parse options. - * - * @param loader - * will be used to load resources - * @param resourceBasename - * a resource name as in - * {@link java.lang.ClassLoader#getResource}, with or without - * extension - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename) { - return parseResourcesAnySyntax(loader, resourceBasename, ConfigParseOptions.defaults()); - } - - /** - * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but - * uses thread's current context class loader if none is set in the - * ConfigParseOptions. - * @param resource the resource name - * @param options parse options - * @return the parsed configuration - */ - public static Config parseResources(String resource, ConfigParseOptions options) { - ConfigParseOptions withLoader = ensureClassLoader(options, "parseResources"); - return Parseable.newResources(resource, withLoader).parse().toConfig(); - } - - /** - * Like {@link #parseResources(ClassLoader,String)} but uses thread's - * current context class loader. - * @param resource the resource name - * @return the parsed configuration - */ - public static Config parseResources(String resource) { - return parseResources(resource, ConfigParseOptions.defaults()); - } - - /** - * Like - * {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} - * but uses thread's current context class loader. - * @param resourceBasename the resource basename (no file type suffix) - * @param options parse options - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(String resourceBasename, ConfigParseOptions options) { - return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options).toConfig(); - } - - /** - * Like {@link #parseResourcesAnySyntax(ClassLoader,String)} but uses - * thread's current context class loader. - * @param resourceBasename the resource basename (no file type suffix) - * @return the parsed configuration - */ - public static Config parseResourcesAnySyntax(String resourceBasename) { - return parseResourcesAnySyntax(resourceBasename, ConfigParseOptions.defaults()); - } - - /** - * Parse only any application replacement (specified by one of config.{resource,file,url}), returning - * an empty Config if no overrides were set. - * - * @since 1.4.1 - * - * @return a {@link java.util.Optional} containing any specified replacement, or {@link Optional#empty()} - * if none was specified. - */ - public static java.util.Optional parseApplicationReplacement() { - return parseApplicationReplacement(ConfigParseOptions.defaults()); - } - - /** - * Like {@link #parseApplicationReplacement()} but allows you to specify a class loader - * ti yse rather than the current context class loader. - * - * @since 1.4.1 - * - * @param loader the class loader - * @return a {@link java.util.Optional} containing any specified replacement, or {@link Optional#empty()} - * if none was specified. - */ - public static java.util.Optional parseApplicationReplacement(ClassLoader loader) { - return parseApplicationReplacement(ConfigParseOptions.defaults().setClassLoader(loader)); - } - - /** - * Like {@link #parseApplicationReplacement()} but allows you to specify parse options. - * - * @since 1.4.1 - * - * @param parseOptions parse options - * @return a {@link java.util.Optional} containing any specified replacement, or {@link Optional#empty()} - * if none was specified. - */ - public static java.util.Optional parseApplicationReplacement(ConfigParseOptions parseOptions) { - final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "parseApplicationReplacement"); - ClassLoader loader = withLoader.getClassLoader(); - - int specified = 0; - - // override application.conf with config.file, config.resource, - // config.url if requested. - String resource = System.getProperty("config.resource"); - if (resource != null) - specified += 1; - String file = System.getProperty("config.file"); - if (file != null) - specified += 1; - String url = System.getProperty("config.url"); - if (url != null) - specified += 1; - - if (specified == 0) { - return java.util.Optional.empty(); - } else if (specified > 1) { - throw new ConfigException.Generic("You set more than one of config.file='" + file - + "', config.url='" + url + "', config.resource='" + resource - + "'; don't know which one to use!"); - } else { - // the override file/url/resource MUST be present or it's an error - ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false); - if (resource != null) { - if (resource.startsWith("/")) - resource = resource.substring(1); - // this deliberately does not parseResourcesAnySyntax; if - // people want that they can use an include statement. - return java.util.Optional.of(ConfigFactory.parseResources(loader, resource, overrideOptions)); - } else if (file != null) { - return java.util.Optional.of(ConfigFactory.parseFile(new File(file), overrideOptions)); - } else { - try { - return java.util.Optional.of(ConfigFactory.parseURL(new URL(url), overrideOptions)); - } catch (MalformedURLException e) { - throw new ConfigException.Generic("Bad URL in config.url system property: '" - + url + "': " + e.getMessage(), e); - } - } - } - - } - - /** - * Parses a string (which should be valid HOCON or JSON by default, or - * the syntax specified in the options otherwise). - * - * @param s string to parse - * @param options parse options - * @return the parsed configuration - */ - public static Config parseString(String s, ConfigParseOptions options) { - return Parseable.newString(s, options).parse().toConfig(); - } - - /** - * Parses a string (which should be valid HOCON or JSON). - * - * @param s string to parse - * @return the parsed configuration - */ - public static Config parseString(String s) { - return parseString(s, ConfigParseOptions.defaults()); - } - - /** - * Creates a {@code Config} based on a {@link java.util.Map} from paths to - * plain Java values. Similar to - * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the - * map are path expressions, rather than keys; and correspondingly it - * returns a {@code Config} instead of a {@code ConfigObject}. This is more - * convenient if you are writing literal maps in code, and less convenient - * if you are getting your maps from some data source such as a parser. - * - *

- * An exception will be thrown (and it is a bug in the caller of the method) - * if a path is both an object and a value, for example if you had both - * "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent - * object of "b". The caller of this method should ensure that doesn't - * happen. - * - * @param values map from paths to plain Java objects - * @param originDescription - * description of what this map represents, like a filename, or - * "default settings" (origin description is used in error - * messages) - * @return the map converted to a {@code Config} - */ - public static Config parseMap(Map values, - String originDescription) { - return ConfigImpl.fromPathMap(values, originDescription).toConfig(); - } - - /** - * See the other overload of {@link #parseMap(Map, String)} for details, - * this one just uses a default origin description. - * - * @param values map from paths to plain Java values - * @return the map converted to a {@code Config} - */ - public static Config parseMap(Map values) { - return parseMap(values, null); - } - - private static com.gu.typesafe.config.ConfigLoadingStrategy getConfigLoadingStrategy() { - String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME); - - if (className != null) { - try { - return ConfigLoadingStrategy.class.cast(Class.forName(className).newInstance()); - } catch (Throwable e) { - throw new ConfigException.BugOrBroken("Failed to load strategy: " + className, e); - } - } else { - return new DefaultConfigLoadingStrategy(); - } - } - - private static Boolean getOverrideWithEnv() { - String overrideWithEnv = System.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME); - - return Boolean.parseBoolean(overrideWithEnv); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncludeContext.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigIncludeContext.java deleted file mode 100644 index cd09e709c1a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncludeContext.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - - -/** - * Context provided to a {@link ConfigIncluder}; this interface is only useful - * inside a {@code ConfigIncluder} implementation, and is not intended for apps - * to implement. - * - *

- * Do not implement this interface; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigIncludeContext { - /** - * Tries to find a name relative to whatever is doing the including, for - * example in the same directory as the file doing the including. Returns - * null if it can't meaningfully create a relative name. The returned - * parseable may not exist; this function is not required to do any IO, just - * compute what the name would be. - * - * The passed-in filename has to be a complete name (with extension), not - * just a basename. (Include statements in config files are allowed to give - * just a basename.) - * - * @param filename - * the name to make relative to the resource doing the including - * @return parseable item relative to the resource doing the including, or - * null - */ - com.gu.typesafe.config.ConfigParseable relativeTo(String filename); - - /** - * Parse options to use (if you use another method to get a - * {@link com.gu.typesafe.config.ConfigParseable} then use {@link ConfigParseable#options()} - * instead though). - * - * @return the parse options - */ - com.gu.typesafe.config.ConfigParseOptions parseOptions(); - - - /** - * Copy this {@link ConfigIncludeContext} giving it a new value for its parseOptions. - * - * @param options new parse options to use - * - * @return the updated copy of this context - */ - ConfigIncludeContext setParseOptions(ConfigParseOptions options); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluder.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluder.java deleted file mode 100644 index 58827486933..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluder.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * Implement this interface and provide an instance to - * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to - * customize handling of {@code include} statements in config files. You may - * also want to implement {@link com.gu.typesafe.config.ConfigIncluderClasspath}, - * {@link com.gu.typesafe.config.ConfigIncluderFile}, and {@link ConfigIncluderURL}, or not. - */ -public interface ConfigIncluder { - /** - * Returns a new includer that falls back to the given includer. This is how - * you can obtain the default includer; it will be provided as a fallback. - * It's up to your includer to chain to it if you want to. You might want to - * merge any files found by the fallback includer with any objects you load - * yourself. - * - * It's important to handle the case where you already have the fallback - * with a "return this", i.e. this method should not create a new object if - * the fallback is the same one you already have. The same fallback may be - * added repeatedly. - * - * @param fallback the previous includer for chaining - * @return a new includer - */ - ConfigIncluder withFallback(ConfigIncluder fallback); - - /** - * Parses another item to be included. The returned object typically would - * not have substitutions resolved. You can throw a ConfigException here to - * abort parsing, or return an empty object, but may not return null. - * - * This method is used for a "heuristic" include statement that does not - * specify file, URL, or classpath resource. If the include statement does - * specify, then the same class implementing {@link ConfigIncluder} must - * also implement {@link ConfigIncluderClasspath}, - * {@link ConfigIncluderFile}, or {@link ConfigIncluderURL} as needed, or a - * default includer will be used. - * - * @param context - * some info about the include context - * @param what - * the include statement's argument - * @return a non-null ConfigObject - */ - ConfigObject include(ConfigIncludeContext context, String what); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderClasspath.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderClasspath.java deleted file mode 100644 index 44a96787668..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderClasspath.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * Implement this in addition to {@link ConfigIncluder} if you want to - * support inclusion of files with the {@code include classpath("resource")} - * syntax. If you do not implement this but do implement {@link ConfigIncluder}, - * attempts to load classpath resources will use the default includer. - */ -public interface ConfigIncluderClasspath { - /** - * Parses another item to be included. The returned object typically would - * not have substitutions resolved. You can throw a ConfigException here to - * abort parsing, or return an empty object, but may not return null. - * - * @param context - * some info about the include context - * @param what - * the include statement's argument - * @return a non-null ConfigObject - */ - ConfigObject includeResources(ConfigIncludeContext context, String what); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderFile.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderFile.java deleted file mode 100644 index 31536ed64a4..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderFile.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.io.File; - -/** - * Implement this in addition to {@link ConfigIncluder} if you want to - * support inclusion of files with the {@code include file("filename")} syntax. - * If you do not implement this but do implement {@link ConfigIncluder}, - * attempts to load files will use the default includer. - */ -public interface ConfigIncluderFile { - /** - * Parses another item to be included. The returned object typically would - * not have substitutions resolved. You can throw a ConfigException here to - * abort parsing, or return an empty object, but may not return null. - * - * @param context - * some info about the include context - * @param what - * the include statement's argument - * @return a non-null ConfigObject - */ - ConfigObject includeFile(ConfigIncludeContext context, File what); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderURL.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderURL.java deleted file mode 100644 index 29850f0487b..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigIncluderURL.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.net.URL; - -/** - * Implement this in addition to {@link com.gu.typesafe.config.ConfigIncluder} if you want to - * support inclusion of files with the {@code include url("http://example.com")} - * syntax. If you do not implement this but do implement {@link ConfigIncluder}, - * attempts to load URLs will use the default includer. - */ -public interface ConfigIncluderURL { - /** - * Parses another item to be included. The returned object typically would - * not have substitutions resolved. You can throw a ConfigException here to - * abort parsing, or return an empty object, but may not return null. - * - * @param context - * some info about the include context - * @param what - * the include statement's argument - * @return a non-null ConfigObject - */ - ConfigObject includeURL(ConfigIncludeContext context, URL what); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigList.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigList.java deleted file mode 100644 index 1214efe9ca5..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigList.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.util.List; - -/** - * Subtype of {@link ConfigValue} representing a list value, as in JSON's - * {@code [1,2,3]} syntax. - * - *

- * {@code ConfigList} implements {@code java.util.List} so you can - * use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the - * list elements into plain Java values. - * - *

- * Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This - * makes it threadsafe and you never have to create "defensive copies." The - * mutator methods from {@link java.util.List} all throw - * {@link java.lang.UnsupportedOperationException}. - * - *

- * The {@link ConfigValue#valueType} method on a list returns - * {@link ConfigValueType#LIST}. - * - *

- * Do not implement {@code ConfigList}; it should only be implemented - * by the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - * - */ -public interface ConfigList extends List, ConfigValue { - - /** - * Recursively unwraps the list, returning a list of plain Java values such - * as Integer or String or whatever is in the list. - * - * @return a {@link java.util.List} containing plain Java objects - */ - @Override - List unwrapped(); - - @Override - ConfigList withOrigin(ConfigOrigin origin); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigLoadingStrategy.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigLoadingStrategy.java deleted file mode 100644 index 3476624914d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigLoadingStrategy.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.gu.typesafe.config; - -/** - * This method allows you to alter default config loading strategy for all the code which - * calls {@link ConfigFactory#load}. - * - * Usually you don't have to implement this interface but it may be required - * when you fixing a improperly implemented library with unavailable source code. - * - * You have to define VM property {@code config.strategy} to replace default strategy with your own. - */ -public interface ConfigLoadingStrategy { - /** - * This method must load and parse application config. - * - * @param parseOptions {@link ConfigParseOptions} to use - * @return loaded config - */ - Config parseApplicationConfig(ConfigParseOptions parseOptions); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigMemorySize.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigMemorySize.java deleted file mode 100644 index a32afd32d01..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigMemorySize.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.math.BigInteger; - -/** - * An immutable class representing an amount of memory. Use - * static factory methods such as {@link - * ConfigMemorySize#ofBytes(BigInteger)} to create instances. - * - * @since 1.3.0 - */ -public final class ConfigMemorySize { - - private BigInteger bytes; - - private ConfigMemorySize(BigInteger bytes) { - if (bytes.signum() < 0) - throw new IllegalArgumentException("Attempt to construct ConfigMemorySize with negative number: " + bytes); - this.bytes = bytes; - } - - /** - * Constructs a ConfigMemorySize representing the given - * number of bytes. - * @since 1.3.0 - * @param bytes a number of bytes - * @return an instance representing the number of bytes - */ - public static ConfigMemorySize ofBytes(BigInteger bytes) { - return new ConfigMemorySize(bytes); - } - - /** - * Constructs a ConfigMemorySize representing the given - * number of bytes. - * @param bytes a number of bytes - * @return an instance representing the number of bytes - */ - public static ConfigMemorySize ofBytes(long bytes) { - return new ConfigMemorySize(BigInteger.valueOf(bytes)); - } - - /** - * Gets the size in bytes. - * - * @since 1.3.0 - * @return how many bytes - * @exception IllegalArgumentException when memory value - * in bytes doesn't fit in a long value. Consider using - * {@link #toBytesBigInteger} in this case. - */ - public long toBytes() { - if (bytes.bitLength() < 64) - return bytes.longValue(); - else - throw new IllegalArgumentException( - "size-in-bytes value is out of range for a 64-bit long: '" + bytes + "'"); - } - - /** - * Gets the size in bytes. The behavior of this method - * is the same as that of the {@link #toBytes()} method, - * except that the number of bytes returned as a - * BigInteger value. Use it when memory value in bytes - * doesn't fit in a long value. - * @return how many bytes - */ - public BigInteger toBytesBigInteger() { - return bytes; - } - - @Override - public String toString() { - return "ConfigMemorySize(" + bytes + ")"; - } - - @Override - public boolean equals(Object other) { - if (other instanceof ConfigMemorySize) { - return ((ConfigMemorySize)other).bytes.equals(this.bytes); - } else { - return false; - } - } - - @Override - public int hashCode() { - // in Java 8 this can become Long.hashCode(bytes) - return bytes.hashCode(); - } - -} - diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigMergeable.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigMergeable.java deleted file mode 100644 index 264b3c0092d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigMergeable.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * Marker for types whose instances can be merged, that is {@link Config} and - * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can - * be combined into a single new instance using the - * {@link ConfigMergeable#withFallback withFallback()} method. - * - *

- * Do not implement this interface; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigMergeable { - /** - * Returns a new value computed by merging this value with another, with - * keys in this value "winning" over the other one. - * - *

- * This associative operation may be used to combine configurations from - * multiple sources (such as multiple configuration files). - * - *

- * The semantics of merging are described in the spec - * for HOCON. Merging typically occurs when either the same object is - * created twice in the same file, or two config files are both loaded. For - * example: - * - *

-     *  foo = { a: 42 }
-     *  foo = { b: 43 }
-     * 
- * - * Here, the two objects are merged as if you had written: - * - *
-     *  foo = { a: 42, b: 43 }
-     * 
- * - *

- * Only {@link ConfigObject} and {@link Config} instances do anything in - * this method (they need to merge the fallback keys into themselves). All - * other values just return the original value, since they automatically - * override any fallback. This means that objects do not merge "across" - * non-objects; if you write - * object.withFallback(nonObject).withFallback(otherObject), - * then otherObject will simply be ignored. This is an - * intentional part of how merging works, because non-objects such as - * strings and integers replace (rather than merging with) any prior value: - * - *

-     * foo = { a: 42 }
-     * foo = 10
-     * 
- * - * Here, the number 10 "wins" and the value of foo would be - * simply 10. Again, for details see the spec. - * - * @param other - * an object whose keys should be used as fallbacks, if the keys - * are not present in this one - * @return a new object (or the original one, if the fallback doesn't get - * used) - */ - ConfigMergeable withFallback(ConfigMergeable other); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigObject.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigObject.java deleted file mode 100644 index 3c1120214b8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigObject.java +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.util.Map; - -/** - * Subtype of {@link com.gu.typesafe.config.ConfigValue} representing an object (AKA dictionary or map) - * value, as in JSON's curly brace { "a" : 42 } syntax. - * - *

- * An object may also be viewed as a {@link Config} by calling - * {@link ConfigObject#toConfig()}. - * - *

- * {@code ConfigObject} implements {@code java.util.Map} so - * you can use it like a regular Java map. Or call {@link #unwrapped()} to - * unwrap the map to a map with plain Java values rather than - * {@code ConfigValue}. - * - *

- * Like all {@link com.gu.typesafe.config.ConfigValue} subtypes, {@code ConfigObject} is immutable. - * This makes it threadsafe and you never have to create "defensive copies." The - * mutator methods from {@link java.util.Map} all throw - * {@link java.lang.UnsupportedOperationException}. - * - *

- * The {@link com.gu.typesafe.config.ConfigValue#valueType} method on an object returns - * {@link ConfigValueType#OBJECT}. - * - *

- * In most cases you want to use the {@link Config} interface rather than this - * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a - * {@code Config}. - * - *

- * The API for a {@code ConfigObject} is in terms of keys, while the API for a - * {@link Config} is in terms of path expressions. Conceptually, - * {@code ConfigObject} is a tree of maps from keys to values, while a - * {@code Config} is a one-level map from paths to values. - * - *

- * Use {@link com.gu.typesafe.config.ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert - * between path expressions and individual path elements (keys). - * - *

- * A {@code ConfigObject} may contain null values, which will have - * {@link com.gu.typesafe.config.ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If - * {@link ConfigObject#get(Object)} returns Java's null then the key was not - * present in the parsed file (or wherever this value tree came from). If - * {@code get("key")} returns a {@link com.gu.typesafe.config.ConfigValue} with type - * {@code ConfigValueType#NULL} then the key was set to null explicitly in the - * config file. - * - *

- * Do not implement interface {@code ConfigObject}; it should only be - * implemented by the config library. Arbitrary implementations will not work - * because the library internals assume a specific concrete implementation. - * Also, this interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigObject extends com.gu.typesafe.config.ConfigValue, Map { - - /** - * Converts this object to a {@link Config} instance, enabling you to use - * path expressions to find values in the object. This is a constant-time - * operation (it is not proportional to the size of the object). - * - * @return a {@link Config} with this object as its root - */ - Config toConfig(); - - /** - * Recursively unwraps the object, returning a map from String to whatever - * plain Java values are unwrapped from the object's values. - * - * @return a {@link java.util.Map} containing plain Java objects - */ - @Override - Map unwrapped(); - - @Override - ConfigObject withFallback(ConfigMergeable other); - - /** - * Gets a {@link com.gu.typesafe.config.ConfigValue} at the given key, or returns null if there is - * no value. The returned {@link com.gu.typesafe.config.ConfigValue} may have - * {@link ConfigValueType#NULL} or any other type, and the passed-in key - * must be a key in this object (rather than a path expression). - * - * @param key - * key to look up - * - * @return the value at the key or null if none - */ - @Override - com.gu.typesafe.config.ConfigValue get(Object key); - - /** - * Clone the object with only the given key (and its children) retained; all - * sibling keys are removed. - * - * @param key - * key to keep - * @return a copy of the object minus all keys except the one specified - */ - ConfigObject withOnlyKey(String key); - - /** - * Clone the object with the given key removed. - * - * @param key - * key to remove - * @return a copy of the object minus the specified key - */ - ConfigObject withoutKey(String key); - - /** - * Returns a {@code ConfigObject} based on this one, but with the given key - * set to the given value. Does not modify this instance (since it's - * immutable). If the key already has a value, that value is replaced. To - * remove a value, use {@link ConfigObject#withoutKey(String)}. - * - * @param key - * key to add - * @param value - * value at the new key - * @return the new instance with the new map entry - */ - ConfigObject withValue(String key, ConfigValue value); - - @Override - ConfigObject withOrigin(ConfigOrigin origin); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigOrigin.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigOrigin.java deleted file mode 100644 index 3d81ad3bf47..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigOrigin.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.net.URL; -import java.util.List; - - -/** - * Represents the origin (such as filename and line number) of a - * {@link com.gu.typesafe.config.ConfigValue} for use in error messages. Obtain the origin of a value - * with {@link com.gu.typesafe.config.ConfigValue#origin}. Exceptions may have an origin, see - * {@link ConfigException#origin}, but be careful because - * ConfigException.origin() may return null. - * - *

- * It's best to use this interface only for debugging; its accuracy is - * "best effort" rather than guaranteed, and a potentially-noticeable amount of - * memory could probably be saved if origins were not kept around, so in the - * future there might be some option to discard origins. - * - *

- * Do not implement this interface; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigOrigin { - /** - * Returns a string describing the origin of a value or exception. This will - * never return null. - * - * @return string describing the origin - */ - public String description(); - - /** - * Returns a filename describing the origin. This will return null if the - * origin was not a file. - * - * @return filename of the origin or null - */ - public String filename(); - - /** - * Returns a URL describing the origin. This will return null if the origin - * has no meaningful URL. - * - * @return url of the origin or null - */ - public URL url(); - - /** - * Returns a classpath resource name describing the origin. This will return - * null if the origin was not a classpath resource. - * - * @return resource name of the origin or null - */ - public String resource(); - - /** - * Returns a line number where the value or exception originated. This will - * return -1 if there's no meaningful line number. - * - * @return line number or -1 if none is available - */ - public int lineNumber(); - - /** - * Returns any comments that appeared to "go with" this place in the file. - * Often an empty list, but never null. The details of this are subject to - * change, but at the moment comments that are immediately before an array - * element or object field, with no blank line after the comment, "go with" - * that element or field. - * - * @return any comments that seemed to "go with" this origin, empty list if - * none - */ - public List comments(); - - /** - * Returns a {@code ConfigOrigin} based on this one, but with the given - * comments. Does not modify this instance or any {@code ConfigValue}s with - * this origin (since they are immutable). To set the returned origin to a - * {@code ConfigValue}, use {@link com.gu.typesafe.config.ConfigValue#withOrigin}. - * - *

- * Note that when the given comments are equal to the comments on this object, - * a new instance may not be created and {@code this} is returned directly. - * - * @since 1.3.0 - * - * @param comments the comments used on the returned origin - * @return the ConfigOrigin with the given comments - */ - public ConfigOrigin withComments(List comments); - - /** - * Returns a {@code ConfigOrigin} based on this one, but with the given - * line number. This origin must be a FILE, URL or RESOURCE. Does not modify - * this instance or any {@code ConfigValue}s with this origin (since they are - * immutable). To set the returned origin to a {@code ConfigValue}, use - * {@link ConfigValue#withOrigin}. - * - *

- * Note that when the given lineNumber are equal to the lineNumber on this - * object, a new instance may not be created and {@code this} is returned - * directly. - * - * @since 1.3.0 - * - * @param lineNumber the new line number - * @return the created ConfigOrigin - */ - public ConfigOrigin withLineNumber(int lineNumber); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigOriginFactory.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigOriginFactory.java deleted file mode 100644 index 223856bedc8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigOriginFactory.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.gu.typesafe.config; - -import java.net.URL; - -import com.gu.typesafe.config.impl.ConfigImpl; - -/** - * This class contains some static factory methods for building a {@link - * com.gu.typesafe.config.ConfigOrigin}. {@code ConfigOrigin}s are automatically created when you - * call other API methods to get a {@code ConfigValue} or {@code Config}. - * But you can also set the origin of an existing {@code ConfigValue}, using - * {@link ConfigValue#withOrigin(com.gu.typesafe.config.ConfigOrigin)}. - * - * @since 1.3.0 - */ -public final class ConfigOriginFactory { - private ConfigOriginFactory() { - } - - /** - * Returns the default origin for values when no other information is - * provided. This is the origin used in {@link ConfigValueFactory - * #fromAnyRef(Object)}. - * - * @since 1.3.0 - * - * @return the default origin - */ - public static com.gu.typesafe.config.ConfigOrigin newSimple() { - return newSimple(null); - } - - /** - * Returns an origin with the given description. - * - * @since 1.3.0 - * - * @param description brief description of what the origin is - * @return a new origin - */ - public static com.gu.typesafe.config.ConfigOrigin newSimple(String description) { - return ConfigImpl.newSimpleOrigin(description); - } - - /** - * Creates a file origin with the given filename. - * - * @since 1.3.0 - * - * @param filename the filename of this origin - * @return a new origin - */ - public static com.gu.typesafe.config.ConfigOrigin newFile(String filename) { - return ConfigImpl.newFileOrigin(filename); - } - - /** - * Creates a url origin with the given URL object. - * - * @since 1.3.0 - * - * @param url the url of this origin - * @return a new origin - */ - public static ConfigOrigin newURL(URL url) { - return ConfigImpl.newURLOrigin(url); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigParseOptions.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigParseOptions.java deleted file mode 100644 index ed5097938e7..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigParseOptions.java +++ /dev/null @@ -1,242 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - - -import com.gu.typesafe.config.impl.ConfigImplUtil; - -/** - * A set of options related to parsing. - * - *

- * This object is immutable, so the "setters" return a new object. - * - *

- * Here is an example of creating a custom {@code ConfigParseOptions}: - * - *

- *     ConfigParseOptions options = ConfigParseOptions.defaults()
- *         .setSyntax(ConfigSyntax.JSON)
- *         .setAllowMissing(false)
- * 
- * - */ -public final class ConfigParseOptions { - final com.gu.typesafe.config.ConfigSyntax syntax; - final String originDescription; - final boolean allowMissing; - final com.gu.typesafe.config.ConfigIncluder includer; - final ClassLoader classLoader; - - private ConfigParseOptions(com.gu.typesafe.config.ConfigSyntax syntax, String originDescription, boolean allowMissing, - com.gu.typesafe.config.ConfigIncluder includer, ClassLoader classLoader) { - this.syntax = syntax; - this.originDescription = originDescription; - this.allowMissing = allowMissing; - this.includer = includer; - this.classLoader = classLoader; - } - - /** - * Gets an instance of ConfigParseOptions with all fields - * set to the default values. Start with this instance and make any - * changes you need. - * @return the default parse options - */ - public static ConfigParseOptions defaults() { - return new ConfigParseOptions(null, null, true, null, null); - } - - /** - * Set the file format. If set to null, try to guess from any available - * filename extension; if guessing fails, assume {@link com.gu.typesafe.config.ConfigSyntax#CONF}. - * - * @param syntax - * a syntax or {@code null} for best guess - * @return options with the syntax set - */ - public ConfigParseOptions setSyntax(com.gu.typesafe.config.ConfigSyntax syntax) { - if (this.syntax == syntax) - return this; - else - return new ConfigParseOptions(syntax, this.originDescription, this.allowMissing, - this.includer, this.classLoader); - } - - /** - * Set the file format. If set to null, assume {@link com.gu.typesafe.config.ConfigSyntax#CONF}. - * - * @param filename - * a configuration file name - * @return options with the syntax set - */ - public ConfigParseOptions setSyntaxFromFilename(String filename) { - com.gu.typesafe.config.ConfigSyntax syntax = ConfigImplUtil.syntaxFromExtension(filename); - return setSyntax(syntax); - } - - /** - * Gets the current syntax option, which may be null for "any". - * @return the current syntax or null - */ - public ConfigSyntax getSyntax() { - return syntax; - } - - /** - * Set a description for the thing being parsed. In most cases this will be - * set up for you to something like the filename, but if you provide just an - * input stream you might want to improve on it. Set to null to allow the - * library to come up with something automatically. This description is the - * basis for the {@link ConfigOrigin} of the parsed values. - * - * @param originDescription description to put in the {@link ConfigOrigin} - * @return options with the origin description set - */ - public ConfigParseOptions setOriginDescription(String originDescription) { - // findbugs complains about == here but is wrong, do not "fix" - if (this.originDescription == originDescription) - return this; - else if (this.originDescription != null && originDescription != null - && this.originDescription.equals(originDescription)) - return this; - else - return new ConfigParseOptions(this.syntax, originDescription, this.allowMissing, - this.includer, this.classLoader); - } - - /** - * Gets the current origin description, which may be null for "automatic". - * @return the current origin description or null - */ - public String getOriginDescription() { - return originDescription; - } - - /** this is package-private, not public API */ - ConfigParseOptions withFallbackOriginDescription(String originDescription) { - if (this.originDescription == null) - return setOriginDescription(originDescription); - else - return this; - } - - /** - * Set to false to throw an exception if the item being parsed (for example - * a file) is missing. Set to true to just return an empty document in that - * case. Note that this setting applies on only to fetching the root document, - * it has no effect on any nested includes. - * - * @param allowMissing true to silently ignore missing item - * @return options with the "allow missing" flag set - */ - public ConfigParseOptions setAllowMissing(boolean allowMissing) { - if (this.allowMissing == allowMissing) - return this; - else - return new ConfigParseOptions(this.syntax, this.originDescription, allowMissing, - this.includer, this.classLoader); - } - - /** - * Gets the current "allow missing" flag. - * @return whether we allow missing files - */ - public boolean getAllowMissing() { - return allowMissing; - } - - /** - * Set a {@link com.gu.typesafe.config.ConfigIncluder} which customizes how includes are handled. - * null means to use the default includer. - * - * @param includer the includer to use or null for default - * @return new version of the parse options with different includer - */ - public ConfigParseOptions setIncluder(com.gu.typesafe.config.ConfigIncluder includer) { - if (this.includer == includer) - return this; - else - return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, - includer, this.classLoader); - } - - /** - * Prepends a {@link com.gu.typesafe.config.ConfigIncluder} which customizes how - * includes are handled. To prepend your includer, the - * library calls {@link com.gu.typesafe.config.ConfigIncluder#withFallback} on your - * includer to append the existing includer to it. - * - * @param includer the includer to prepend (may not be null) - * @return new version of the parse options with different includer - */ - public ConfigParseOptions prependIncluder(com.gu.typesafe.config.ConfigIncluder includer) { - if (includer == null) - throw new NullPointerException("null includer passed to prependIncluder"); - if (this.includer == includer) - return this; - else if (this.includer != null) - return setIncluder(includer.withFallback(this.includer)); - else - return setIncluder(includer); - } - - /** - * Appends a {@link com.gu.typesafe.config.ConfigIncluder} which customizes how - * includes are handled. To append, the library calls {@link - * com.gu.typesafe.config.ConfigIncluder#withFallback} on the existing includer. - * - * @param includer the includer to append (may not be null) - * @return new version of the parse options with different includer - */ - public ConfigParseOptions appendIncluder(com.gu.typesafe.config.ConfigIncluder includer) { - if (includer == null) - throw new NullPointerException("null includer passed to appendIncluder"); - if (this.includer == includer) - return this; - else if (this.includer != null) - return setIncluder(this.includer.withFallback(includer)); - else - return setIncluder(includer); - } - - /** - * Gets the current includer (will be null for the default includer). - * @return current includer or null - */ - public ConfigIncluder getIncluder() { - return includer; - } - - /** - * Set the class loader. If set to null, - * Thread.currentThread().getContextClassLoader() will be used. - * - * @param loader - * a class loader or {@code null} to use thread context class - * loader - * @return options with the class loader set - */ - public ConfigParseOptions setClassLoader(ClassLoader loader) { - if (this.classLoader == loader) - return this; - else - return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, - this.includer, loader); - } - - /** - * Get the class loader; never returns {@code null}, if the class loader was - * unset, returns - * Thread.currentThread().getContextClassLoader(). - * - * @return class loader to use - */ - public ClassLoader getClassLoader() { - if (this.classLoader == null) - return Thread.currentThread().getContextClassLoader(); - else - return this.classLoader; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigParseable.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigParseable.java deleted file mode 100644 index 777b1034b8c..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigParseable.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - - -/** - * An opaque handle to something that can be parsed, obtained from - * {@link ConfigIncludeContext}. - * - *

- * Do not implement this interface; it should only be implemented by - * the config library. Arbitrary implementations will not work because the - * library internals assume a specific concrete implementation. Also, this - * interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigParseable { - /** - * Parse whatever it is. The options should come from - * {@link ConfigParseable#options options()} but you could tweak them if you - * like. - * - * @param options - * parse options, should be based on the ones from - * {@link ConfigParseable#options options()} - * @return the parsed object - */ - ConfigObject parse(ConfigParseOptions options); - - /** - * Returns a {@link ConfigOrigin} describing the origin of the parseable - * item. - * @return the origin of the parseable item - */ - ConfigOrigin origin(); - - /** - * Get the initial options, which can be modified then passed to parse(). - * These options will have the right description, includer, and other - * parameters already set up. - * @return the initial options - */ - ConfigParseOptions options(); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigRenderOptions.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigRenderOptions.java deleted file mode 100644 index c131ad2c923..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigRenderOptions.java +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - * THIS FILE HAS BEEN MODIFIED TO ADD SUPPORT FOR COMPACT KEYS - */ -package com.gu.typesafe.config; - -/** - *

- * A set of options related to rendering a {@link ConfigValue}. Passed to - * {@link ConfigValue#render(ConfigRenderOptions)}. - * - *

- * Here is an example of creating a {@code ConfigRenderOptions}: - * - *

- *     ConfigRenderOptions options =
- *         ConfigRenderOptions.defaults().setComments(false)
- * 
- */ -public final class ConfigRenderOptions { - private final boolean originComments; - private final boolean comments; - private final boolean formatted; - private final boolean json; - private final boolean compactKeys; - - private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted, - boolean json, boolean compactKeys) { - this.originComments = originComments; - this.comments = comments; - this.formatted = formatted; - this.json = json; - this.compactKeys = compactKeys; - } - - /** - * Returns the default render options which are verbose (commented and - * formatted). See {@link ConfigRenderOptions#concise} for stripped-down - * options. This rendering will not be valid JSON since it has comments. - * - * @return the default render options - */ - public static ConfigRenderOptions defaults() { - return new ConfigRenderOptions(true, true, true, true, false); - } - - /** - * Returns concise render options (no whitespace or comments). For a - * resolved {@link Config}, the concise rendering will be valid JSON. - * - * @return the concise render options - */ - public static ConfigRenderOptions concise() { - return new ConfigRenderOptions(false, false, false, true, false); - } - - /** - * Returns options with comments toggled. This controls human-written - * comments but not the autogenerated "origin of this setting" comments, - * which are controlled by {@link ConfigRenderOptions#setOriginComments}. - * - * @param value - * true to include comments in the render - * @return options with requested setting for comments - */ - public ConfigRenderOptions setComments(boolean value) { - if (value == comments) - return this; - else - return new ConfigRenderOptions(originComments, value, formatted, json, compactKeys); - } - - /** - * Returns whether the options enable comments. This method is mostly used - * by the config lib internally, not by applications. - * - * @return true if comments should be rendered - */ - public boolean getComments() { - return comments; - } - - /** - * Returns options with origin comments toggled. If this is enabled, the - * library generates comments for each setting based on the - * {@link ConfigValue#origin} of that setting's value. For example these - * comments might tell you which file a setting comes from. - * - *

- * {@code setOriginComments()} controls only these autogenerated - * "origin of this setting" comments, to toggle regular comments use - * {@link ConfigRenderOptions#setComments}. - * - * @param value - * true to include autogenerated setting-origin comments in the - * render - * @return options with origin comments toggled - */ - public ConfigRenderOptions setOriginComments(boolean value) { - if (value == originComments) - return this; - else - return new ConfigRenderOptions(value, comments, formatted, json, compactKeys); - } - - /** - * Returns whether the options enable automated origin comments. This method - * is mostly used by the config lib internally, not by applications. - * - * @return true if origin comments should be rendered - */ - public boolean getOriginComments() { - return originComments; - } - - /** - * Returns options with formatting toggled. Formatting means indentation and - * whitespace, enabling formatting makes things prettier but larger. - * - * @param value - * true to enable formatting - * @return options with requested setting for formatting - */ - public ConfigRenderOptions setFormatted(boolean value) { - if (value == formatted) - return this; - else - return new ConfigRenderOptions(originComments, comments, value, json, compactKeys); - } - - /** - * Returns whether the options enable formatting. This method is mostly used - * by the config lib internally, not by applications. - * - * @return true if the options enable formatting - */ - public boolean getFormatted() { - return formatted; - } - - /** - * Returns options with JSON toggled. JSON means that HOCON extensions - * (omitting commas, quotes for example) won't be used. However, whether to - * use comments is controlled by the separate {@link #setComments(boolean)} - * and {@link #setOriginComments(boolean)} options. So if you enable - * comments you will get invalid JSON despite setting this to true. - * - * @param value - * true to include non-JSON extensions in the render - * @return options with requested setting for JSON - */ - public ConfigRenderOptions setJson(boolean value) { - if (value == json) - return this; - else - return new ConfigRenderOptions(originComments, comments, formatted, value, compactKeys); - } - - /** - * Returns whether the options enable JSON. This method is mostly used by - * the config lib internally, not by applications. - * - * @return true if only JSON should be rendered - */ - public boolean getJson() { - return json; - } - - /** - * Returns options with compact keys toggled. Compact keys means that when an - * object only has one member it will be rendered using the dot notation. - * - * i.e. rather than {@code key { key2=value }} you will get {@code key.key2=value} - * - * @param value - * true to compact the object keys - * @return options with requested setting for compact keys - */ - public ConfigRenderOptions setCompactKeys(boolean value) { - if (value == compactKeys) - return this; - else - return new ConfigRenderOptions(originComments, comments, formatted, json, value); - } - - /** - * Returns whether the options enable compact keys. This method is mostly used by - * the config lib internally, not by applications. - * - * @return true if object keys should be rendered compactly - */ - public boolean getCompactKeys() { - return compactKeys; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("ConfigRenderOptions("); - if (originComments) - sb.append("originComments,"); - if (comments) - sb.append("comments,"); - if (formatted) - sb.append("formatted,"); - if (json) - sb.append("json,"); - if (compactKeys) - sb.append("compactKeys,"); - if (sb.charAt(sb.length() - 1) == ',') - sb.setLength(sb.length() - 1); - sb.append(")"); - return sb.toString(); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigResolveOptions.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigResolveOptions.java deleted file mode 100644 index a0eb7613f54..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigResolveOptions.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * A set of options related to resolving substitutions. Substitutions use the - * ${foo.bar} syntax and are documented in the HOCON - * spec. - *

- * Typically this class would be used with the method - * {@link Config#resolve(ConfigResolveOptions)}. - *

- * This object is immutable, so the "setters" return a new object. - *

- * Here is an example of creating a custom {@code ConfigResolveOptions}: - * - *

- *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
- *         .setUseSystemEnvironment(false)
- * 
- *

- * In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt - * {@link ConfigResolveOptions#noSystem} which avoids looking at any system - * environment variables or other external system information. (Right now, - * environment variables are the only example.) - */ -public final class ConfigResolveOptions { - private final boolean useSystemEnvironment; - private final boolean allowUnresolved; - private final ConfigResolver resolver; - - private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved, - ConfigResolver resolver) { - this.useSystemEnvironment = useSystemEnvironment; - this.allowUnresolved = allowUnresolved; - this.resolver = resolver; - } - - /** - * Returns the default resolve options. By default the system environment - * will be used and unresolved substitutions are not allowed. - * - * @return the default resolve options - */ - public static ConfigResolveOptions defaults() { - return new ConfigResolveOptions(true, false, NULL_RESOLVER); - } - - /** - * Returns resolve options that disable any reference to "system" data - * (currently, this means environment variables). - * - * @return the resolve options with env variables disabled - */ - public static ConfigResolveOptions noSystem() { - return defaults().setUseSystemEnvironment(false); - } - - /** - * Returns options with use of environment variables set to the given value. - * - * @param value - * true to resolve substitutions falling back to environment - * variables. - * @return options with requested setting for use of environment variables - */ - public ConfigResolveOptions setUseSystemEnvironment(boolean value) { - return new ConfigResolveOptions(value, allowUnresolved, resolver); - } - - /** - * Returns whether the options enable use of system environment variables. - * This method is mostly used by the config lib internally, not by - * applications. - * - * @return true if environment variables should be used - */ - public boolean getUseSystemEnvironment() { - return useSystemEnvironment; - } - - /** - * Returns options with "allow unresolved" set to the given value. By - * default, unresolved substitutions are an error. If unresolved - * substitutions are allowed, then a future attempt to use the unresolved - * value may fail, but {@link Config#resolve(ConfigResolveOptions)} itself - * will not throw. - * - * @param value - * true to silently ignore unresolved substitutions. - * @return options with requested setting for whether to allow substitutions - * @since 1.2.0 - */ - public ConfigResolveOptions setAllowUnresolved(boolean value) { - return new ConfigResolveOptions(useSystemEnvironment, value, resolver); - } - - /** - * Returns options where the given resolver used as a fallback if a - * reference cannot be otherwise resolved. This resolver will only be called - * after resolution has failed to substitute with a value from within the - * config itself and with any other resolvers that have been appended before - * this one. Multiple resolvers can be added using, - * - *

-     *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
-     *         .appendResolver(primary)
-     *         .appendResolver(secondary)
-     *         .appendResolver(tertiary);
-     * 
- * - * With this config unresolved references will first be resolved with the - * primary resolver, if that fails then the secondary, and finally if that - * also fails the tertiary. - * - * If all fallbacks fail to return a substitution "allow unresolved" - * determines whether resolution fails or continues. - *` - * @param value the resolver to fall back to - * @return options that use the given resolver as a fallback - * @since 1.3.2 - */ - public ConfigResolveOptions appendResolver(ConfigResolver value) { - if (value == null) { - throw new ConfigException.BugOrBroken("null resolver passed to appendResolver"); - } else if (value == this.resolver) { - return this; - } else { - return new ConfigResolveOptions(useSystemEnvironment, allowUnresolved, - this.resolver.withFallback(value)); - } - } - - /** - * Returns the resolver to use as a fallback if a substitution cannot be - * otherwise resolved. Never returns null. This method is mostly used by the - * config lib internally, not by applications. - * - * @return the non-null fallback resolver - * @since 1.3.2 - */ - public ConfigResolver getResolver() { - return this.resolver; - } - - /** - * Returns whether the options allow unresolved substitutions. This method - * is mostly used by the config lib internally, not by applications. - * - * @return true if unresolved substitutions are allowed - * @since 1.2.0 - */ - public boolean getAllowUnresolved() { - return allowUnresolved; - } - - /** - * Singleton resolver that never resolves paths. - */ - private static final ConfigResolver NULL_RESOLVER = new ConfigResolver() { - - @Override - public ConfigValue lookup(String path) { - return null; - } - - @Override - public ConfigResolver withFallback(ConfigResolver fallback) { - return fallback; - } - - }; - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigResolver.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigResolver.java deleted file mode 100644 index 3771d9db05d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigResolver.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.gu.typesafe.config; - -/** - * Implement this interface and provide an instance to - * {@link ConfigResolveOptions#appendResolver ConfigResolveOptions.appendResolver()} - * to provide custom behavior when unresolved substitutions are encountered - * during resolution. - * @since 1.3.2 - */ -public interface ConfigResolver { - - /** - * Returns the value to substitute for the given unresolved path. To get the - * components of the path use {@link ConfigUtil#splitPath(String)}. If a - * non-null value is returned that value will be substituted, otherwise - * resolution will continue to consider the substitution as still - * unresolved. - * - * @param path the unresolved path - * @return the value to use as a substitution or null - */ - public ConfigValue lookup(String path); - - /** - * Returns a new resolver that falls back to the given resolver if this - * one doesn't provide a substitution itself. - * - * It's important to handle the case where you already have the fallback - * with a "return this", i.e. this method should not create a new object if - * the fallback is the same one you already have. The same fallback may be - * added repeatedly. - * - * @param fallback the previous includer for chaining - * @return a new resolver - */ - public ConfigResolver withFallback(ConfigResolver fallback); - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigSyntax.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigSyntax.java deleted file mode 100644 index e30da93def1..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigSyntax.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * The syntax of a character stream (JSON, HOCON - * aka ".conf", or Java properties). - * - */ -public enum ConfigSyntax { - /** - * Pedantically strict JSON format; no - * comments, no unexpected commas, no duplicate keys in the same object. - * Associated with the .json file extension and - * application/json Content-Type. - */ - JSON, - /** - * The JSON-superset HOCON format. Associated with the .conf file extension - * and application/hocon Content-Type. - */ - CONF, - /** - * Standard Java properties format. Associated with the .properties - * file extension and text/x-java-properties Content-Type. - */ - PROPERTIES; -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigUtil.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigUtil.java deleted file mode 100644 index 2b1f089a7c2..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.gu.typesafe.config; - -import java.util.List; - -import com.gu.typesafe.config.impl.ConfigImplUtil; - -/** - * Contains static utility methods. - * - */ -public final class ConfigUtil { - private ConfigUtil() { - - } - - /** - * Quotes and escapes a string, as in the JSON specification. - * - * @param s - * a string - * @return the string quoted and escaped - */ - public static String quoteString(String s) { - return ConfigImplUtil.renderJsonString(s); - } - - /** - * Converts a list of keys to a path expression, by quoting the path - * elements as needed and then joining them separated by a period. A path - * expression is usable with a {@link Config}, while individual path - * elements are usable with a {@link ConfigObject}. - *

- * See the overview documentation for {@link Config} for more detail on path - * expressions vs. keys. - * - * @param elements - * the keys in the path - * @return a path expression - * @throws ConfigException - * if there are no elements - */ - public static String joinPath(String... elements) { - return ConfigImplUtil.joinPath(elements); - } - - /** - * Converts a list of strings to a path expression, by quoting the path - * elements as needed and then joining them separated by a period. A path - * expression is usable with a {@link Config}, while individual path - * elements are usable with a {@link ConfigObject}. - *

- * See the overview documentation for {@link Config} for more detail on path - * expressions vs. keys. - * - * @param elements - * the keys in the path - * @return a path expression - * @throws ConfigException - * if the list is empty - */ - public static String joinPath(List elements) { - return ConfigImplUtil.joinPath(elements); - } - - /** - * Converts a path expression into a list of keys, by splitting on period - * and unquoting the individual path elements. A path expression is usable - * with a {@link Config}, while individual path elements are usable with a - * {@link ConfigObject}. - *

- * See the overview documentation for {@link Config} for more detail on path - * expressions vs. keys. - * - * @param path - * a path expression - * @return the individual keys in the path - * @throws ConfigException - * if the path expression is invalid - */ - public static List splitPath(String path) { - return ConfigImplUtil.splitPath(path); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigValue.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigValue.java deleted file mode 100644 index df89cc4e36e..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigValue.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * An immutable value, following the JSON type - * schema. - * - *

- * Because this object is immutable, it is safe to use from multiple threads and - * there's no need for "defensive copies." - * - *

- * Do not implement interface {@code ConfigValue}; it should only be - * implemented by the config library. Arbitrary implementations will not work - * because the library internals assume a specific concrete implementation. - * Also, this interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigValue extends com.gu.typesafe.config.ConfigMergeable { - /** - * The origin of the value (file, line number, etc.), for debugging and - * error messages. - * - * @return where the value came from - */ - ConfigOrigin origin(); - - /** - * The {@link ConfigValueType} of the value; matches the JSON type schema. - * - * @return value's type - */ - ConfigValueType valueType(); - - /** - * Returns the value as a plain Java boxed value, that is, a {@code String}, - * {@code Number}, {@code Boolean}, {@code Map}, - * {@code List}, or {@code null}, matching the {@link #valueType()} - * of this {@code ConfigValue}. If the value is a {@link ConfigObject} or - * {@link ConfigList}, it is recursively unwrapped. - * @return a plain Java value corresponding to this ConfigValue - */ - Object unwrapped(); - - /** - * Renders the config value as a HOCON string. This method is primarily - * intended for debugging, so it tries to add helpful comments and - * whitespace. - * - *

- * If the config value has not been resolved (see {@link Config#resolve}), - * it's possible that it can't be rendered as valid HOCON. In that case the - * rendering should still be useful for debugging but you might not be able - * to parse it. If the value has been resolved, it will always be parseable. - * - *

- * This method is equivalent to - * {@code render(ConfigRenderOptions.defaults())}. - * - * @return the rendered value - */ - String render(); - - /** - * Renders the config value to a string, using the provided options. - * - *

- * If the config value has not been resolved (see {@link Config#resolve}), - * it's possible that it can't be rendered as valid HOCON. In that case the - * rendering should still be useful for debugging but you might not be able - * to parse it. If the value has been resolved, it will always be parseable. - * - *

- * If the config value has been resolved and the options disable all - * HOCON-specific features (such as comments), the rendering will be valid - * JSON. If you enable HOCON-only features such as comments, the rendering - * will not be valid JSON. - * - * @param options - * the rendering options - * @return the rendered value - */ - String render(ConfigRenderOptions options); - - @Override - ConfigValue withFallback(ConfigMergeable other); - - /** - * Places the value inside a {@link Config} at the given path. See also - * {@link ConfigValue#atKey(String)}. - * - * @param path - * path to store this value at. - * @return a {@code Config} instance containing this value at the given - * path. - */ - Config atPath(String path); - - /** - * Places the value inside a {@link Config} at the given key. See also - * {@link ConfigValue#atPath(String)}. - * - * @param key - * key to store this value at. - * @return a {@code Config} instance containing this value at the given key. - */ - Config atKey(String key); - - /** - * Returns a {@code ConfigValue} based on this one, but with the given - * origin. This is useful when you are parsing a new format of file or setting - * comments for a single ConfigValue. - * - * @since 1.3.0 - * - * @param origin the origin set on the returned value - * @return the new ConfigValue with the given origin - */ - ConfigValue withOrigin(ConfigOrigin origin); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigValueFactory.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigValueFactory.java deleted file mode 100644 index c5e1393320d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigValueFactory.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -import java.util.Map; - -import com.gu.typesafe.config.impl.ConfigImpl; - -/** - * This class holds some static factory methods for building {@link ConfigValue} - * instances. See also {@link ConfigFactory} which has methods for parsing files - * and certain in-memory data structures. - */ -public final class ConfigValueFactory { - private ConfigValueFactory() { - } - - /** - * Creates a {@link ConfigValue} from a plain Java boxed value, which may be - * a Boolean, Number, String, - * Map, Iterable, or null. A - * Map must be a Map from String to more values - * that can be supplied to fromAnyRef(). An - * Iterable must iterate over more values that can be supplied - * to fromAnyRef(). A Map will become a - * {@link ConfigObject} and an Iterable will become a - * {@link com.gu.typesafe.config.ConfigList}. If the Iterable is not an ordered - * collection, results could be strange, since ConfigList is - * ordered. - * - *

- * In a Map passed to fromAnyRef(), the map's keys - * are plain keys, not path expressions. So if your Map has a - * key "foo.bar" then you will get one object with a key called "foo.bar", - * rather than an object with a key "foo" containing another object with a - * key "bar". - * - *

- * The originDescription will be used to set the origin() field on the - * ConfigValue. It should normally be the name of the file the values came - * from, or something short describing the value such as "default settings". - * The originDescription is prefixed to error messages so users can tell - * where problematic values are coming from. - * - *

- * Supplying the result of ConfigValue.unwrapped() to this function is - * guaranteed to work and should give you back a ConfigValue that matches - * the one you unwrapped. The re-wrapped ConfigValue will lose some - * information that was present in the original such as its origin, but it - * will have matching values. - * - *

- * If you pass in a ConfigValue to this - * function, it will be returned unmodified. (The - * originDescription will be ignored in this - * case.) - * - *

- * This function throws if you supply a value that cannot be converted to a - * ConfigValue, but supplying such a value is a bug in your program, so you - * should never handle the exception. Just fix your program (or report a bug - * against this library). - * - * @param object - * object to convert to ConfigValue - * @param originDescription - * name of origin file or brief description of what the value is - * @return a new value - */ - public static ConfigValue fromAnyRef(Object object, String originDescription) { - return ConfigImpl.fromAnyRef(object, originDescription); - } - - /** - * See the {@link #fromAnyRef(Object,String)} documentation for details. - * This is a typesafe wrapper that only works on {@link java.util.Map} and - * returns {@link ConfigObject} rather than {@link ConfigValue}. - * - *

- * If your Map has a key "foo.bar" then you will get one object - * with a key called "foo.bar", rather than an object with a key "foo" - * containing another object with a key "bar". The keys in the map are keys; - * not path expressions. That is, the Map corresponds exactly - * to a single {@code ConfigObject}. The keys will not be parsed or - * modified, and the values are wrapped in ConfigValue. To get nested - * {@code ConfigObject}, some of the values in the map would have to be more - * maps. - * - *

- * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the - * keys in the map as path expressions. - * - * @param values map from keys to plain Java values - * @param originDescription description to use in {@link ConfigOrigin} of created values - * @return a new {@link ConfigObject} value - */ - public static ConfigObject fromMap(Map values, - String originDescription) { - return (ConfigObject) fromAnyRef(values, originDescription); - } - - /** - * See the {@link #fromAnyRef(Object,String)} documentation for details. - * This is a typesafe wrapper that only works on {@link java.lang.Iterable} - * and returns {@link com.gu.typesafe.config.ConfigList} rather than {@link ConfigValue}. - * - * @param values list of plain Java values - * @param originDescription description to use in {@link ConfigOrigin} of created values - * @return a new {@link com.gu.typesafe.config.ConfigList} value - */ - public static com.gu.typesafe.config.ConfigList fromIterable(Iterable values, - String originDescription) { - return (com.gu.typesafe.config.ConfigList) fromAnyRef(values, originDescription); - } - - /** - * See the other overload {@link #fromAnyRef(Object,String)} for details, - * this one just uses a default origin description. - * - * @param object a plain Java value - * @return a new {@link ConfigValue} - */ - public static ConfigValue fromAnyRef(Object object) { - return fromAnyRef(object, null); - } - - /** - * See the other overload {@link #fromMap(Map,String)} for details, this one - * just uses a default origin description. - * - *

- * See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in - * the map as path expressions. - * - * @param values map from keys to plain Java values - * @return a new {@link ConfigObject} - */ - public static ConfigObject fromMap(Map values) { - return fromMap(values, null); - } - - /** - * See the other overload of {@link #fromIterable(Iterable, String)} for - * details, this one just uses a default origin description. - * - * @param values list of plain Java values - * @return a new {@link com.gu.typesafe.config.ConfigList} - */ - public static ConfigList fromIterable(Iterable values) { - return fromIterable(values, null); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/ConfigValueType.java b/scripts/src/main/java/com/gu/typesafe/config/ConfigValueType.java deleted file mode 100644 index 828b6b20955..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/ConfigValueType.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config; - -/** - * The type of a configuration value (following the JSON type schema). - */ -public enum ConfigValueType { - OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/DefaultConfigLoadingStrategy.java b/scripts/src/main/java/com/gu/typesafe/config/DefaultConfigLoadingStrategy.java deleted file mode 100644 index 253c7b85b34..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/DefaultConfigLoadingStrategy.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.gu.typesafe.config; - -/** - * Default config loading strategy. Able to load resource, file or URL. - * Behavior may be altered by defining one of VM properties - * {@code config.resource}, {@code config.file} or {@code config.url} - */ -public class DefaultConfigLoadingStrategy implements ConfigLoadingStrategy { - @Override - public Config parseApplicationConfig(ConfigParseOptions parseOptions) { - return com.gu.typesafe.config.ConfigFactory.parseApplicationReplacement(parseOptions) - .orElseGet(() -> ConfigFactory.parseResourcesAnySyntax("application", parseOptions)); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/Optional.java b/scripts/src/main/java/com/gu/typesafe/config/Optional.java deleted file mode 100644 index f0c6a5386a3..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/Optional.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.gu.typesafe.config; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Allows an config property to be {@code null}. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -public @interface Optional { - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNode.java b/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNode.java deleted file mode 100644 index 4d9c2e5371c..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNode.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.parser.ConfigNode; - -import java.util.Collection; - -abstract class AbstractConfigNode implements ConfigNode { - abstract Collection tokens(); - final public String render() { - StringBuilder origText = new StringBuilder(); - Iterable tokens = tokens(); - for (Token t : tokens) { - origText.append(t.tokenText()); - } - return origText.toString(); - } - - @Override - final public boolean equals(Object other) { - return other instanceof AbstractConfigNode && render().equals(((AbstractConfigNode)other).render()); - } - - @Override - final public int hashCode() { - return render().hashCode(); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNodeValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNodeValue.java deleted file mode 100644 index cc01d896cc8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigNodeValue.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -// This is required if we want -// to be referencing the AbstractConfigNode class in implementation rather than the -// ConfigNode interface, as we can't cast an AbstractConfigNode to an interface -abstract class AbstractConfigNodeValue extends AbstractConfigNode { - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigObject.java b/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigObject.java deleted file mode 100644 index 2e1d3550fb9..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigObject.java +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import com.gu.typesafe.config.*; -import com.gu.typesafe.config.ConfigMergeable; -import com.gu.typesafe.config.ConfigRenderOptions; - -abstract class AbstractConfigObject extends com.gu.typesafe.config.impl.AbstractConfigValue implements com.gu.typesafe.config.ConfigObject, com.gu.typesafe.config.impl.Container { - final private com.gu.typesafe.config.impl.SimpleConfig config; - - protected AbstractConfigObject(com.gu.typesafe.config.ConfigOrigin origin) { - super(origin); - this.config = new com.gu.typesafe.config.impl.SimpleConfig(this); - } - - @Override - public com.gu.typesafe.config.impl.SimpleConfig toConfig() { - return config; - } - - @Override - public AbstractConfigObject toFallbackValue() { - return this; - } - - @Override - abstract public AbstractConfigObject withOnlyKey(String key); - - @Override - abstract public AbstractConfigObject withoutKey(String key); - - @Override - abstract public AbstractConfigObject withValue(String key, com.gu.typesafe.config.ConfigValue value); - - abstract protected AbstractConfigObject withOnlyPathOrNull(Path path); - - abstract AbstractConfigObject withOnlyPath(Path path); - - abstract AbstractConfigObject withoutPath(Path path); - - abstract AbstractConfigObject withValue(Path path, com.gu.typesafe.config.ConfigValue value); - - /** - * This looks up the key with no transformation or type conversion of any - * kind, and returns null if the key is not present. The object must be - * resolved along the nodes needed to get the key or - * ConfigException.NotResolved will be thrown. - * - * @param key - * @return the unmodified raw value or null - */ - protected final com.gu.typesafe.config.impl.AbstractConfigValue peekAssumingResolved(String key, Path originalPath) { - try { - return attemptPeekWithPartialResolve(key); - } catch (com.gu.typesafe.config.ConfigException.NotResolved e) { - throw com.gu.typesafe.config.impl.ConfigImpl.improveNotResolved(originalPath, e); - } - } - - /** - * Look up the key on an only-partially-resolved object, with no - * transformation or type conversion of any kind; if 'this' is not resolved - * then try to look up the key anyway if possible. - * - * @param key - * key to look up - * @return the value of the key, or null if known not to exist - * @throws com.gu.typesafe.config.ConfigException.NotResolved - * if can't figure out key's value (or existence) without more - * resolving - */ - abstract com.gu.typesafe.config.impl.AbstractConfigValue attemptPeekWithPartialResolve(String key); - - /** - * Looks up the path with no transformation or type conversion. Returns null - * if the path is not found; throws ConfigException.NotResolved if we need - * to go through an unresolved node to look up the path. - */ - protected com.gu.typesafe.config.impl.AbstractConfigValue peekPath(Path path) { - return peekPath(this, path); - } - - private static com.gu.typesafe.config.impl.AbstractConfigValue peekPath(AbstractConfigObject self, Path path) { - try { - // we'll fail if anything along the path can't - // be looked at without resolving. - Path next = path.remainder(); - com.gu.typesafe.config.impl.AbstractConfigValue v = self.attemptPeekWithPartialResolve(path.first()); - - if (next == null) { - return v; - } else { - if (v instanceof AbstractConfigObject) { - return peekPath((AbstractConfigObject) v, next); - } else { - return null; - } - } - } catch (com.gu.typesafe.config.ConfigException.NotResolved e) { - throw ConfigImpl.improveNotResolved(path, e); - } - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.OBJECT; - } - - protected abstract AbstractConfigObject newCopy(ResolveStatus status, com.gu.typesafe.config.ConfigOrigin origin); - - @Override - protected AbstractConfigObject newCopy(com.gu.typesafe.config.ConfigOrigin origin) { - return newCopy(resolveStatus(), origin); - } - - @Override - protected AbstractConfigObject constructDelayedMerge(com.gu.typesafe.config.ConfigOrigin origin, - List stack) { - return new com.gu.typesafe.config.impl.ConfigDelayedMergeObject(origin, stack); - } - - @Override - protected abstract AbstractConfigObject mergedWithObject(AbstractConfigObject fallback); - - @Override - public AbstractConfigObject withFallback(ConfigMergeable mergeable) { - return (AbstractConfigObject) super.withFallback(mergeable); - } - - static com.gu.typesafe.config.ConfigOrigin mergeOrigins( - Collection stack) { - if (stack.isEmpty()) - throw new ConfigException.BugOrBroken( - "can't merge origins on empty list"); - List origins = new ArrayList(); - com.gu.typesafe.config.ConfigOrigin firstOrigin = null; - int numMerged = 0; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : stack) { - if (firstOrigin == null) - firstOrigin = v.origin(); - - if (v instanceof AbstractConfigObject - && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED - && ((ConfigObject) v).isEmpty()) { - // don't include empty files or the .empty() - // config in the description, since they are - // likely to be "implementation details" - } else { - origins.add(v.origin()); - numMerged += 1; - } - } - - if (numMerged == 0) { - // the configs were all empty, so just use the first one - origins.add(firstOrigin); - } - - return com.gu.typesafe.config.impl.SimpleConfigOrigin.mergeOrigins(origins); - } - - static com.gu.typesafe.config.ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { - return mergeOrigins(Arrays.asList(stack)); - } - - @Override - abstract com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, - com.gu.typesafe.config.impl.ResolveSource source) - throws NotPossibleToResolve; - - @Override - abstract AbstractConfigObject relativized(final Path prefix); - - @Override - public abstract com.gu.typesafe.config.impl.AbstractConfigValue get(Object key); - - @Override - protected abstract void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options); - - private static UnsupportedOperationException weAreImmutable(String method) { - return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map." - + method); - } - - @Override - public void clear() { - throw weAreImmutable("clear"); - } - - @Override - public com.gu.typesafe.config.ConfigValue put(String arg0, com.gu.typesafe.config.ConfigValue arg1) { - throw weAreImmutable("put"); - } - - @Override - public void putAll(Map arg0) { - throw weAreImmutable("putAll"); - } - - @Override - public ConfigValue remove(Object arg0) { - throw weAreImmutable("remove"); - } - - @Override - public AbstractConfigObject withOrigin(ConfigOrigin origin) { - return (AbstractConfigObject) super.withOrigin(origin); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigValue.java deleted file mode 100644 index 36cd2300f22..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/AbstractConfigValue.java +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - * THIS FILE HAS BEEN MODIFIED TO ADD SUPPORT FOR COMPACT KEYS - */ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.gu.typesafe.config.*; - -/** - * - * Trying very hard to avoid a parent reference in config values; when you have - * a tree like this, the availability of parent() tends to result in a lot of - * improperly-factored and non-modular code. Please don't add parent(). - * - */ -abstract class AbstractConfigValue implements com.gu.typesafe.config.ConfigValue, MergeableValue { - - final private SimpleConfigOrigin origin; - - AbstractConfigValue(com.gu.typesafe.config.ConfigOrigin origin) { - this.origin = (SimpleConfigOrigin) origin; - } - - @Override - public SimpleConfigOrigin origin() { - return this.origin; - } - - /** - * This exception means that a value is inherently not resolveable, at the - * moment the only known cause is a cycle of substitutions. This is a - * checked exception since it's internal to the library and we want to be - * sure we handle it before passing it out to public API. This is only - * supposed to be thrown by the target of a cyclic reference and it's - * supposed to be caught by the ConfigReference looking up that reference, - * so it should be impossible for an outermost resolve() to throw this. - * - * Contrast with ConfigException.NotResolved which just means nobody called - * resolve(). - */ - static class NotPossibleToResolve extends Exception { - private static final long serialVersionUID = 1L; - - final private String traceString; - - NotPossibleToResolve(ResolveContext context) { - super("was not possible to resolve"); - this.traceString = context.traceString(); - } - - String traceString() { - return traceString; - } - } - - /** - * Called only by ResolveContext.resolve(). - * - * @param context - * state of the current resolve - * @param source - * where to look up values - * @return a new value if there were changes, or this if no changes - */ - ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) - throws NotPossibleToResolve { - return ResolveResult.make(context, this); - } - - ResolveStatus resolveStatus() { - return ResolveStatus.RESOLVED; - } - - protected static List replaceChildInList(List list, - AbstractConfigValue child, AbstractConfigValue replacement) { - int i = 0; - while (i < list.size() && list.get(i) != child) - ++i; - if (i == list.size()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to replace " + child + " which is not in " + list); - List newStack = new ArrayList(list); - if (replacement != null) - newStack.set(i, replacement); - else - newStack.remove(i); - - if (newStack.isEmpty()) - return null; - else - return newStack; - } - - protected static boolean hasDescendantInList(List list, AbstractConfigValue descendant) { - for (AbstractConfigValue v : list) { - if (v == descendant) - return true; - } - // now the expensive traversal - for (AbstractConfigValue v : list) { - if (v instanceof Container && ((Container) v).hasDescendant(descendant)) - return true; - } - return false; - } - - /** - * This is used when including one file in another; the included file is - * relativized to the path it's included into in the parent file. The point - * is that if you include a file at foo.bar in the parent, and the included - * file as a substitution ${a.b.c}, the included substitution now needs to - * be ${foo.bar.a.b.c} because we resolve substitutions globally only after - * parsing everything. - * - * @param prefix - * @return value relativized to the given path or the same value if nothing - * to do - */ - AbstractConfigValue relativized(Path prefix) { - return this; - } - - protected interface Modifier { - // keyOrNull is null for non-objects - AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) - throws Exception; - } - - protected abstract class NoExceptionsModifier implements Modifier { - @Override - public final AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) - throws Exception { - try { - return modifyChild(keyOrNull, v); - } catch (RuntimeException e) { - throw e; - } catch(Exception e) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Unexpected exception", e); - } - } - - abstract AbstractConfigValue modifyChild(String keyOrNull, AbstractConfigValue v); - } - - @Override - public AbstractConfigValue toFallbackValue() { - return this; - } - - protected abstract AbstractConfigValue newCopy(com.gu.typesafe.config.ConfigOrigin origin); - - // this is virtualized rather than a field because only some subclasses - // really need to store the boolean, and they may be able to pack it - // with another boolean to save space. - protected boolean ignoresFallbacks() { - // if we are not resolved, then somewhere in this value there's - // a substitution that may need to look at the fallbacks. - return resolveStatus() == ResolveStatus.RESOLVED; - } - - protected AbstractConfigValue withFallbacksIgnored() { - if (ignoresFallbacks()) - return this; - else - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "value class doesn't implement forced fallback-ignoring " + this); - } - - // the withFallback() implementation is supposed to avoid calling - // mergedWith* if we're ignoring fallbacks. - protected final void requireNotIgnoringFallbacks() { - if (ignoresFallbacks()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "method should not have been called with ignoresFallbacks=true " - + getClass().getSimpleName()); - } - - protected AbstractConfigValue constructDelayedMerge(com.gu.typesafe.config.ConfigOrigin origin, - List stack) { - return new ConfigDelayedMerge(origin, stack); - } - - protected final AbstractConfigValue mergedWithTheUnmergeable( - Collection stack, Unmergeable fallback) { - requireNotIgnoringFallbacks(); - - // if we turn out to be an object, and the fallback also does, - // then a merge may be required; delay until we resolve. - List newStack = new ArrayList(); - newStack.addAll(stack); - newStack.addAll(fallback.unmergedValues()); - return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); - } - - private final AbstractConfigValue delayMerge(Collection stack, - AbstractConfigValue fallback) { - // if we turn out to be an object, and the fallback also does, - // then a merge may be required. - // if we contain a substitution, resolving it may need to look - // back to the fallback. - List newStack = new ArrayList(); - newStack.addAll(stack); - newStack.add(fallback); - return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); - } - - protected final AbstractConfigValue mergedWithObject(Collection stack, - AbstractConfigObject fallback) { - requireNotIgnoringFallbacks(); - - if (this instanceof AbstractConfigObject) - throw new ConfigException.BugOrBroken("Objects must reimplement mergedWithObject"); - - return mergedWithNonObject(stack, fallback); - } - - protected final AbstractConfigValue mergedWithNonObject(Collection stack, - AbstractConfigValue fallback) { - requireNotIgnoringFallbacks(); - - if (resolveStatus() == ResolveStatus.RESOLVED) { - // falling back to a non-object doesn't merge anything, and also - // prohibits merging any objects that we fall back to later. - // so we have to switch to ignoresFallbacks mode. - return withFallbacksIgnored(); - } else { - // if unresolved, we may have to look back to fallbacks as part of - // the resolution process, so always delay - return delayMerge(stack, fallback); - } - } - - protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) { - requireNotIgnoringFallbacks(); - - return mergedWithTheUnmergeable(Collections.singletonList(this), fallback); - } - - protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) { - requireNotIgnoringFallbacks(); - - return mergedWithObject(Collections.singletonList(this), fallback); - } - - protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) { - requireNotIgnoringFallbacks(); - - return mergedWithNonObject(Collections.singletonList(this), fallback); - } - - @Override - public AbstractConfigValue withOrigin(com.gu.typesafe.config.ConfigOrigin origin) { - if (this.origin == origin) - return this; - else - return newCopy(origin); - } - - // this is only overridden to change the return type - @Override - public AbstractConfigValue withFallback(ConfigMergeable mergeable) { - if (ignoresFallbacks()) { - return this; - } else { - com.gu.typesafe.config.ConfigValue other = ((MergeableValue) mergeable).toFallbackValue(); - - if (other instanceof Unmergeable) { - return mergedWithTheUnmergeable((Unmergeable) other); - } else if (other instanceof AbstractConfigObject) { - return mergedWithObject((AbstractConfigObject) other); - } else { - return mergedWithNonObject((AbstractConfigValue) other); - } - } - } - - protected boolean canEqual(Object other) { - return other instanceof com.gu.typesafe.config.ConfigValue; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof com.gu.typesafe.config.ConfigValue) { - return canEqual(other) - && (this.valueType() == - ((com.gu.typesafe.config.ConfigValue) other).valueType()) - && ConfigImplUtil.equalsHandlingNull(this.unwrapped(), - ((ConfigValue) other).unwrapped()); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - Object o = this.unwrapped(); - if (o == null) - return 0; - else - return o.hashCode(); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - render(sb, 0, true /* atRoot */, null /* atKey */, com.gu.typesafe.config.ConfigRenderOptions.concise()); - return getClass().getSimpleName() + "(" + sb.toString() + ")"; - } - - protected static void indent(StringBuilder sb, int indent, com.gu.typesafe.config.ConfigRenderOptions options) { - if (options.getFormatted()) { - int remaining = indent; - while (remaining > 0) { - sb.append(" "); - --remaining; - } - } - } - - protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, com.gu.typesafe.config.ConfigRenderOptions options) { - if (atKey != null) { - String renderedKey; - if (options.getJson()) - renderedKey = ConfigImplUtil.renderJsonString(atKey); - else - renderedKey = ConfigImplUtil.renderStringUnquotedIfPossible(atKey); - - sb.append(renderedKey); - - if (options.getJson()) { - if (options.getFormatted()) - sb.append(" : "); - else - sb.append(":"); - } else { - // in non-JSON we can omit the colon or equals before an object - if (this instanceof ConfigObject) { - ConfigObject configObj = (ConfigObject) this; - boolean singleton = configObj.keySet().size() == 1; - boolean shouldCompact = singleton && options.getCompactKeys(); - if (options.getFormatted() && !shouldCompact) - sb.append(' '); - } else { - sb.append("="); - } - } - } - render(sb, indent, atRoot, options); - } - - protected void render(StringBuilder sb, int indent, boolean atRoot, com.gu.typesafe.config.ConfigRenderOptions options) { - Object u = unwrapped(); - sb.append(u.toString()); - } - - @Override - public final String render() { - return render(com.gu.typesafe.config.ConfigRenderOptions.defaults()); - } - - @Override - public final String render(ConfigRenderOptions options) { - StringBuilder sb = new StringBuilder(); - render(sb, 0, true, null, options); - return sb.toString(); - } - - // toString() is a debugging-oriented string but this is defined - // to create a string that would parse back to the value in JSON. - // It only works for primitive values (that would be a single token) - // which are auto-converted to strings when concatenating with - // other strings or by the DefaultTransformer. - String transformToString() { - return null; - } - - SimpleConfig atKey(com.gu.typesafe.config.ConfigOrigin origin, String key) { - Map m = Collections.singletonMap(key, this); - return (new SimpleConfigObject(origin, m)).toConfig(); - } - - @Override - public SimpleConfig atKey(String key) { - return atKey(SimpleConfigOrigin.newSimple("atKey(" + key + ")"), key); - } - - SimpleConfig atPath(ConfigOrigin origin, Path path) { - Path parent = path.parent(); - SimpleConfig result = atKey(origin, path.last()); - while (parent != null) { - String key = parent.last(); - result = result.atKey(origin, key); - parent = parent.parent(); - } - return result; - } - - @Override - public SimpleConfig atPath(String pathExpression) { - SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); - return atPath(origin, Path.newPath(pathExpression)); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/BadMap.java b/scripts/src/main/java/com/gu/typesafe/config/impl/BadMap.java deleted file mode 100644 index a0b68a8e525..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/BadMap.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.gu.typesafe.config.impl; - -/** - * A terrible Map that isn't as expensive as HashMap to copy and - * add one item to. Please write something real if you see this - * and get cranky. - */ -final class BadMap { - final static class Entry { - final int hash; - final Object key; - final Object value; - final Entry next; - - Entry(int hash, Object k, Object v, Entry next) { - this.hash = hash; - this.key = k; - this.value = v; - this.next = next; - } - - Object find(Object k) { - if (key.equals(k)) - return value; - else if (next != null) - return next.find(k); - else - return null; - } - } - - private final int size; - private final Entry[] entries; - - private final static Entry[] emptyEntries = {}; - - BadMap() { - this(0, emptyEntries); - } - - private BadMap(int size, Entry[] entries) { - this.size = size; - this.entries = entries; - } - - BadMap copyingPut(K k, V v) { - int newSize = size + 1; - Entry[] newEntries; - if (newSize > entries.length) { - // nextPrime doesn't always return a prime larger than - // we passed in, so this block may not actually change - // the entries size. the "-1" is to ensure we use - // array length 2 when going from 0 to 1. - newEntries = new Entry[nextPrime((newSize * 2) - 1)]; - } else { - newEntries = new Entry[entries.length]; - } - - if (newEntries.length == entries.length) { - System.arraycopy(entries, 0, newEntries, 0, entries.length); - } else { - rehash(entries, newEntries); - } - - int hash = Math.abs(k.hashCode()); - store(newEntries, hash, k, v); - return new BadMap<>(newSize, newEntries); - } - - private static void store(Entry[] entries, int hash, K k, V v) { - int i = hash % entries.length; - Entry old = entries[i]; // old may be null - entries[i] = new Entry(hash, k, v, old); - } - - private static void store(Entry[] entries, Entry e) { - int i = e.hash % entries.length; - Entry old = entries[i]; // old may be null - if (old == null && e.next == null) { - // share the entry since it has no "next" - entries[i] = e; - } else { - // bah, have to copy it - entries[i] = new Entry(e.hash, e.key, e.value, old); - } - } - - private static void rehash(Entry[] src, Entry[] dest) { - for (Entry entry : src) { - // have to store each "next" element individually; they may belong in different indices - while (entry != null) { - store(dest, entry); - entry = entry.next; - } - } - } - - @SuppressWarnings("unchecked") - V get(K k) { - if (entries.length == 0) { - return null; - } else { - int hash = Math.abs(k.hashCode()); - int i = hash % entries.length; - Entry e = entries[i]; - if (e == null) - return null; - else - return (V) e.find(k); - } - } - - - private final static int[] primes = { - /* Skip some early ones that are close together */ - 2, /* 3, */ 5, /* 7, */ 11, /* 13, */ 17, /* 19, */ 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, - 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, - 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, - 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, - 947, 953, 967, 971, 977, 983, 991, 997, 1009, - /* now we start skipping some, this is arbitrary */ - 2053, 3079, 4057, 7103, 10949, 16069, 32609, 65867, 104729 - }; - - private static int nextPrime(int i) { - for (int p : primes) { - if (p > i) - return p; - } - /* oh well */ - return primes[primes.length - 1]; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBeanImpl.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBeanImpl.java deleted file mode 100644 index 9acfc1223e1..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBeanImpl.java +++ /dev/null @@ -1,299 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.time.Duration; -import java.util.Set; - -import com.gu.typesafe.config.*; -import com.gu.typesafe.config.ConfigMemorySize; - -/** - * Internal implementation detail, not ABI stable, do not touch. - * For use only by the {@link com.typesafe.config} package. - */ -public class ConfigBeanImpl { - - /** - * This is public ONLY for use by the "config" package, DO NOT USE this ABI - * may change. - * @param type of the bean - * @param config config to use - * @param clazz class of the bean - * @return the bean instance - */ - public static T createInternal(com.gu.typesafe.config.Config config, Class clazz) { - if (((com.gu.typesafe.config.impl.SimpleConfig)config).root().resolveStatus() != ResolveStatus.RESOLVED) - throw new com.gu.typesafe.config.ConfigException.NotResolved( - "need to Config#resolve() a config before using it to initialize a bean, see the API docs for Config#resolve()"); - - Map configProps = new HashMap(); - Map originalNames = new HashMap(); - for (Map.Entry configProp : config.root().entrySet()) { - String originalName = configProp.getKey(); - String camelName = ConfigImplUtil.toCamelCase(originalName); - // if a setting is in there both as some hyphen name and the camel name, - // the camel one wins - if (originalNames.containsKey(camelName) && !originalName.equals(camelName)) { - // if we aren't a camel name to start with, we lose. - // if we are or we are the first matching key, we win. - } else { - configProps.put(camelName, (com.gu.typesafe.config.impl.AbstractConfigValue) configProp.getValue()); - originalNames.put(camelName, originalName); - } - } - - BeanInfo beanInfo = null; - try { - beanInfo = Introspector.getBeanInfo(clazz); - } catch (IntrospectionException e) { - throw new com.gu.typesafe.config.ConfigException.BadBean("Could not get bean information for class " + clazz.getName(), e); - } - - try { - List beanProps = new ArrayList(); - for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { - if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { - continue; - } - beanProps.add(beanProp); - } - - // Try to throw all validation issues at once (this does not comprehensively - // find every issue, but it should find common ones). - List problems = new ArrayList(); - for (PropertyDescriptor beanProp : beanProps) { - Method setter = beanProp.getWriteMethod(); - Class parameterClass = setter.getParameterTypes()[0]; - - com.gu.typesafe.config.ConfigValueType expectedType = getValueTypeOrNull(parameterClass); - if (expectedType != null) { - String name = originalNames.get(beanProp.getName()); - if (name == null) - name = beanProp.getName(); - Path path = Path.newKey(name); - com.gu.typesafe.config.impl.AbstractConfigValue configValue = configProps.get(beanProp.getName()); - if (configValue != null) { - com.gu.typesafe.config.impl.SimpleConfig.checkValid(path, expectedType, configValue, problems); - } else { - if (!isOptionalProperty(clazz, beanProp)) { - com.gu.typesafe.config.impl.SimpleConfig.addMissing(problems, expectedType, path, config.origin()); - } - } - } - } - - if (!problems.isEmpty()) { - throw new com.gu.typesafe.config.ConfigException.ValidationFailed(problems); - } - - // Fill in the bean instance - T bean = clazz.newInstance(); - for (PropertyDescriptor beanProp : beanProps) { - Method setter = beanProp.getWriteMethod(); - Type parameterType = setter.getGenericParameterTypes()[0]; - Class parameterClass = setter.getParameterTypes()[0]; - String configPropName = originalNames.get(beanProp.getName()); - // Is the property key missing in the config? - if (configPropName == null) { - // If so, continue if the field is marked as @{link Optional} - if (isOptionalProperty(clazz, beanProp)) { - continue; - } - // Otherwise, raise a {@link Missing} exception right here - throw new com.gu.typesafe.config.ConfigException.Missing(beanProp.getName()); - } - Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName); - setter.invoke(bean, unwrapped); - } - return bean; - } catch (InstantiationException e) { - throw new com.gu.typesafe.config.ConfigException.BadBean(clazz.getName() + " needs a public no-args constructor to be used as a bean", e); - } catch (IllegalAccessException e) { - throw new com.gu.typesafe.config.ConfigException.BadBean(clazz.getName() + " getters and setters are not accessible, they must be for use as a bean", e); - } catch (InvocationTargetException e) { - throw new com.gu.typesafe.config.ConfigException.BadBean("Calling bean method on " + clazz.getName() + " caused an exception", e); - } - } - - // we could magically make this work in many cases by doing - // getAnyRef() (or getValue().unwrapped()), but anytime we - // rely on that, we aren't doing the type conversions Config - // usually does, and we will throw ClassCastException instead - // of a nicer error message giving the name of the bad - // setting. So, instead, we only support a limited number of - // types plus you can always use Object, ConfigValue, Config, - // ConfigObject, etc. as an escape hatch. - private static Object getValue(Class beanClass, Type parameterType, Class parameterClass, com.gu.typesafe.config.Config config, - String configPropName) { - if (parameterClass == Boolean.class || parameterClass == boolean.class) { - return config.getBoolean(configPropName); - } else if (parameterClass == Integer.class || parameterClass == int.class) { - return config.getInt(configPropName); - } else if (parameterClass == Double.class || parameterClass == double.class) { - return config.getDouble(configPropName); - } else if (parameterClass == Long.class || parameterClass == long.class) { - return config.getLong(configPropName); - } else if (parameterClass == String.class) { - return config.getString(configPropName); - } else if (parameterClass == Duration.class) { - return config.getDuration(configPropName); - } else if (parameterClass == ConfigMemorySize.class) { - return config.getMemorySize(configPropName); - } else if (parameterClass == Object.class) { - return config.getAnyRef(configPropName); - } else if (parameterClass == List.class) { - return getListValue(beanClass, parameterType, parameterClass, config, configPropName); - } else if (parameterClass == Set.class) { - return getSetValue(beanClass, parameterType, parameterClass, config, configPropName); - } else if (parameterClass == Map.class) { - // we could do better here, but right now we don't. - Type[] typeArgs = ((ParameterizedType)parameterType).getActualTypeArguments(); - if (typeArgs[0] != String.class || typeArgs[1] != Object.class) { - throw new com.gu.typesafe.config.ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported Map<" + typeArgs[0] + "," + typeArgs[1] + ">, only Map is supported right now"); - } - return config.getObject(configPropName).unwrapped(); - } else if (parameterClass == com.gu.typesafe.config.Config.class) { - return config.getConfig(configPropName); - } else if (parameterClass == com.gu.typesafe.config.ConfigObject.class) { - return config.getObject(configPropName); - } else if (parameterClass == com.gu.typesafe.config.ConfigValue.class) { - return config.getValue(configPropName); - } else if (parameterClass == com.gu.typesafe.config.ConfigList.class) { - return config.getList(configPropName); - } else if (parameterClass.isEnum()) { - @SuppressWarnings("unchecked") - Enum enumValue = config.getEnum((Class) parameterClass, configPropName); - return enumValue; - } else if (hasAtLeastOneBeanProperty(parameterClass)) { - return createInternal(config.getConfig(configPropName), parameterClass); - } else { - throw new com.gu.typesafe.config.ConfigException.BadBean("Bean property " + configPropName + " of class " + beanClass.getName() + " has unsupported type " + parameterType); - } - } - - private static Object getSetValue(Class beanClass, Type parameterType, Class parameterClass, com.gu.typesafe.config.Config config, String configPropName) { - return new HashSet((List) getListValue(beanClass, parameterType, parameterClass, config, configPropName)); - } - - private static Object getListValue(Class beanClass, Type parameterType, Class parameterClass, com.gu.typesafe.config.Config config, String configPropName) { - Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0]; - - if (elementType == Boolean.class) { - return config.getBooleanList(configPropName); - } else if (elementType == Integer.class) { - return config.getIntList(configPropName); - } else if (elementType == Double.class) { - return config.getDoubleList(configPropName); - } else if (elementType == Long.class) { - return config.getLongList(configPropName); - } else if (elementType == String.class) { - return config.getStringList(configPropName); - } else if (elementType == Duration.class) { - return config.getDurationList(configPropName); - } else if (elementType == ConfigMemorySize.class) { - return config.getMemorySizeList(configPropName); - } else if (elementType == Object.class) { - return config.getAnyRefList(configPropName); - } else if (elementType == com.gu.typesafe.config.Config.class) { - return config.getConfigList(configPropName); - } else if (elementType == com.gu.typesafe.config.ConfigObject.class) { - return config.getObjectList(configPropName); - } else if (elementType == ConfigValue.class) { - return config.getList(configPropName); - } else if (((Class) elementType).isEnum()) { - @SuppressWarnings("unchecked") - List enumValues = config.getEnumList((Class) elementType, configPropName); - return enumValues; - } else if (hasAtLeastOneBeanProperty((Class) elementType)) { - List beanList = new ArrayList(); - List configList = config.getConfigList(configPropName); - for (com.gu.typesafe.config.Config listMember : configList) { - beanList.add(createInternal(listMember, (Class) elementType)); - } - return beanList; - } else { - throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType); - } - } - - // null if we can't easily say; this is heuristic/best-effort - private static com.gu.typesafe.config.ConfigValueType getValueTypeOrNull(Class parameterClass) { - if (parameterClass == Boolean.class || parameterClass == boolean.class) { - return com.gu.typesafe.config.ConfigValueType.BOOLEAN; - } else if (parameterClass == Integer.class || parameterClass == int.class) { - return com.gu.typesafe.config.ConfigValueType.NUMBER; - } else if (parameterClass == Double.class || parameterClass == double.class) { - return com.gu.typesafe.config.ConfigValueType.NUMBER; - } else if (parameterClass == Long.class || parameterClass == long.class) { - return com.gu.typesafe.config.ConfigValueType.NUMBER; - } else if (parameterClass == String.class) { - return com.gu.typesafe.config.ConfigValueType.STRING; - } else if (parameterClass == Duration.class) { - return null; - } else if (parameterClass == ConfigMemorySize.class) { - return null; - } else if (parameterClass == List.class) { - return com.gu.typesafe.config.ConfigValueType.LIST; - } else if (parameterClass == Map.class) { - return com.gu.typesafe.config.ConfigValueType.OBJECT; - } else if (parameterClass == Config.class) { - return com.gu.typesafe.config.ConfigValueType.OBJECT; - } else if (parameterClass == ConfigObject.class) { - return com.gu.typesafe.config.ConfigValueType.OBJECT; - } else if (parameterClass == ConfigList.class) { - return ConfigValueType.LIST; - } else { - return null; - } - } - - private static boolean hasAtLeastOneBeanProperty(Class clazz) { - BeanInfo beanInfo = null; - try { - beanInfo = Introspector.getBeanInfo(clazz); - } catch (IntrospectionException e) { - return false; - } - - for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { - if (beanProp.getReadMethod() != null && beanProp.getWriteMethod() != null) { - return true; - } - } - - return false; - } - - private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) { - Field field = getField(beanClass, beanProp.getName()); - return field != null ? field.getAnnotationsByType(com.gu.typesafe.config.Optional.class).length > 0 : beanProp.getReadMethod().getAnnotationsByType(Optional.class).length > 0; - } - - private static Field getField(Class beanClass, String fieldName) { - try { - Field field = beanClass.getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } catch (NoSuchFieldException e) { - // Don't give up yet. Try to look for field in super class, if any. - } - beanClass = beanClass.getSuperclass(); - if (beanClass == null) { - return null; - } - return getField(beanClass, fieldName); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBoolean.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBoolean.java deleted file mode 100644 index e85f0f29b17..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigBoolean.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; - -final class ConfigBoolean extends com.gu.typesafe.config.impl.AbstractConfigValue implements Serializable { - - private static final long serialVersionUID = 2L; - - final private boolean value; - - ConfigBoolean(com.gu.typesafe.config.ConfigOrigin origin, boolean value) { - super(origin); - this.value = value; - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.BOOLEAN; - } - - @Override - public Boolean unwrapped() { - return value; - } - - @Override - String transformToString() { - return value ? "true" : "false"; - } - - @Override - protected ConfigBoolean newCopy(ConfigOrigin origin) { - return new ConfigBoolean(origin, value); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigConcatenation.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigConcatenation.java deleted file mode 100644 index 92fadfb2956..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigConcatenation.java +++ /dev/null @@ -1,293 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigObject; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; -import com.gu.typesafe.config.ConfigRenderOptions; - -/** - * A ConfigConcatenation represents a list of values to be concatenated (see the - * spec). It only has to exist if at least one value is an unresolved - * substitution, otherwise we could go ahead and collapse the list into a single - * value. - * - * Right now this is always a list of strings and ${} references, but in the - * future should support a list of ConfigList. We may also support - * concatenations of objects, but ConfigDelayedMerge should be used for that - * since a concat of objects really will merge, not concatenate. - */ -final class ConfigConcatenation extends com.gu.typesafe.config.impl.AbstractConfigValue implements com.gu.typesafe.config.impl.Unmergeable, Container { - - final private List pieces; - - ConfigConcatenation(com.gu.typesafe.config.ConfigOrigin origin, List pieces) { - super(origin); - this.pieces = pieces; - - if (pieces.size() < 2) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Created concatenation with less than 2 items: " - + this); - - boolean hadUnmergeable = false; - for (com.gu.typesafe.config.impl.AbstractConfigValue p : pieces) { - if (p instanceof ConfigConcatenation) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "ConfigConcatenation should never be nested: " + this); - if (p instanceof com.gu.typesafe.config.impl.Unmergeable) - hadUnmergeable = true; - } - if (!hadUnmergeable) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "Created concatenation without an unmergeable in it: " + this); - } - - private com.gu.typesafe.config.ConfigException.NotResolved notResolved() { - return new com.gu.typesafe.config.ConfigException.NotResolved( - "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " - + this); - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - throw notResolved(); - } - - @Override - public Object unwrapped() { - throw notResolved(); - } - - @Override - protected ConfigConcatenation newCopy(com.gu.typesafe.config.ConfigOrigin newOrigin) { - return new ConfigConcatenation(newOrigin, pieces); - } - - @Override - protected boolean ignoresFallbacks() { - // we can never ignore fallbacks because if a child ConfigReference - // is self-referential we have to look lower in the merge stack - // for its value. - return false; - } - - @Override - public Collection unmergedValues() { - return Collections.singleton(this); - } - - private static boolean isIgnoredWhitespace(com.gu.typesafe.config.impl.AbstractConfigValue value) { - return (value instanceof ConfigString) && !((ConfigString)value).wasQuoted(); - } - - /** - * Add left and right, or their merger, to builder. - */ - private static void join(ArrayList builder, com.gu.typesafe.config.impl.AbstractConfigValue origRight) { - com.gu.typesafe.config.impl.AbstractConfigValue left = builder.get(builder.size() - 1); - com.gu.typesafe.config.impl.AbstractConfigValue right = origRight; - - // check for an object which can be converted to a list - // (this will be an object with numeric keys, like foo.0, foo.1) - if (left instanceof com.gu.typesafe.config.ConfigObject && right instanceof SimpleConfigList) { - left = DefaultTransformer.transform(left, com.gu.typesafe.config.ConfigValueType.LIST); - } else if (left instanceof SimpleConfigList && right instanceof com.gu.typesafe.config.ConfigObject) { - right = DefaultTransformer.transform(right, ConfigValueType.LIST); - } - - // Since this depends on the type of two instances, I couldn't think - // of much alternative to an instanceof chain. Visitors are sometimes - // used for multiple dispatch but seems like overkill. - com.gu.typesafe.config.impl.AbstractConfigValue joined = null; - if (left instanceof com.gu.typesafe.config.ConfigObject && right instanceof com.gu.typesafe.config.ConfigObject) { - joined = right.withFallback(left); - } else if (left instanceof SimpleConfigList && right instanceof SimpleConfigList) { - joined = ((SimpleConfigList)left).concatenate((SimpleConfigList)right); - } else if ((left instanceof SimpleConfigList || left instanceof ConfigObject) && - isIgnoredWhitespace(right)) { - joined = left; - // it should be impossible that left is whitespace and right is a list or object - } else if (left instanceof ConfigConcatenation || right instanceof ConfigConcatenation) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("unflattened ConfigConcatenation"); - } else if (left instanceof com.gu.typesafe.config.impl.Unmergeable || right instanceof com.gu.typesafe.config.impl.Unmergeable) { - // leave joined=null, cannot join - } else { - // handle primitive type or primitive type mixed with object or list - String s1 = left.transformToString(); - String s2 = right.transformToString(); - if (s1 == null || s2 == null) { - throw new com.gu.typesafe.config.ConfigException.WrongType(left.origin(), - "Cannot concatenate object or list with a non-object-or-list, " + left - + " and " + right + " are not compatible"); - } else { - com.gu.typesafe.config.ConfigOrigin joinedOrigin = SimpleConfigOrigin.mergeOrigins(left.origin(), - right.origin()); - joined = new ConfigString.Quoted(joinedOrigin, s1 + s2); - } - } - - if (joined == null) { - builder.add(right); - } else { - builder.remove(builder.size() - 1); - builder.add(joined); - } - } - - static List consolidate(List pieces) { - if (pieces.size() < 2) { - return pieces; - } else { - List flattened = new ArrayList(pieces.size()); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : pieces) { - if (v instanceof ConfigConcatenation) { - flattened.addAll(((ConfigConcatenation) v).pieces); - } else { - flattened.add(v); - } - } - - ArrayList consolidated = new ArrayList( - flattened.size()); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : flattened) { - if (consolidated.isEmpty()) - consolidated.add(v); - else - join(consolidated, v); - } - - return consolidated; - } - } - - static com.gu.typesafe.config.impl.AbstractConfigValue concatenate(List pieces) { - List consolidated = consolidate(pieces); - if (consolidated.isEmpty()) { - return null; - } else if (consolidated.size() == 1) { - return consolidated.get(0); - } else { - ConfigOrigin mergedOrigin = SimpleConfigOrigin.mergeOrigins(consolidated); - return new ConfigConcatenation(mergedOrigin, consolidated); - } - } - - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, ResolveSource source) - throws NotPossibleToResolve { - if (ConfigImpl.traceSubstitutionsEnabled()) { - int indent = context.depth() + 2; - ConfigImpl.trace(indent - 1, "concatenation has " + pieces.size() + " pieces:"); - int count = 0; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : pieces) { - ConfigImpl.trace(indent, count + ": " + v); - count += 1; - } - } - - // Right now there's no reason to pushParent here because the - // content of ConfigConcatenation should not need to replaceChild, - // but if it did we'd have to do this. - ResolveSource sourceWithParent = source; // .pushParent(this); - com.gu.typesafe.config.impl.ResolveContext newContext = context; - - List resolved = new ArrayList(pieces.size()); - for (com.gu.typesafe.config.impl.AbstractConfigValue p : pieces) { - // to concat into a string we have to do a full resolve, - // so unrestrict the context, then put restriction back afterward - Path restriction = newContext.restrictToChild(); - com.gu.typesafe.config.impl.ResolveResult result = newContext.unrestricted() - .resolve(p, sourceWithParent); - com.gu.typesafe.config.impl.AbstractConfigValue r = result.value; - newContext = result.context.restrict(restriction); - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "resolved concat piece to " + r); - if (r == null) { - // it was optional... omit - } else { - resolved.add(r); - } - } - - // now need to concat everything - List joined = consolidate(resolved); - // if unresolved is allowed we can just become another - // ConfigConcatenation - if (joined.size() > 1 && context.options().getAllowUnresolved()) - return com.gu.typesafe.config.impl.ResolveResult.make(newContext, new ConfigConcatenation(this.origin(), joined)); - else if (joined.isEmpty()) - // we had just a list of optional references using ${?} - return com.gu.typesafe.config.impl.ResolveResult.make(newContext, null); - else if (joined.size() == 1) - return com.gu.typesafe.config.impl.ResolveResult.make(newContext, joined.get(0)); - else - throw new ConfigException.BugOrBroken("Bug in the library; resolved list was joined to too many values: " - + joined); - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.UNRESOLVED; - } - - @Override - public ConfigConcatenation replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - List newPieces = replaceChildInList(pieces, child, replacement); - if (newPieces == null) - return null; - else - return new ConfigConcatenation(origin(), newPieces); - } - - @Override - public boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant) { - return hasDescendantInList(pieces, descendant); - } - - // when you graft a substitution into another object, - // you have to prefix it with the location in that object - // where you grafted it; but save prefixLength so - // system property and env variable lookups don't get - // broken. - @Override - ConfigConcatenation relativized(Path prefix) { - List newPieces = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigValue p : pieces) { - newPieces.add(p.relativized(prefix)); - } - return new ConfigConcatenation(origin(), newPieces); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof ConfigConcatenation; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigConcatenation) { - return canEqual(other) && this.pieces.equals(((ConfigConcatenation) other).pieces); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - return pieces.hashCode(); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - for (com.gu.typesafe.config.impl.AbstractConfigValue p : pieces) { - p.render(sb, indent, atRoot, options); - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMerge.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMerge.java deleted file mode 100644 index fec5b6f4bdd..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMerge.java +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; -import com.gu.typesafe.config.ConfigRenderOptions; - -/** - * The issue here is that we want to first merge our stack of config files, and - * then we want to evaluate substitutions. But if two substitutions both expand - * to an object, we might need to merge those two objects. Thus, we can't ever - * "override" a substitution when we do a merge; instead we have to save the - * stack of values that should be merged, and resolve the merge when we evaluate - * substitutions. - */ -final class ConfigDelayedMerge extends com.gu.typesafe.config.impl.AbstractConfigValue implements com.gu.typesafe.config.impl.Unmergeable, - com.gu.typesafe.config.impl.ReplaceableMergeStack { - - // earlier items in the stack win - final private List stack; - - ConfigDelayedMerge(com.gu.typesafe.config.ConfigOrigin origin, List stack) { - super(origin); - this.stack = stack; - if (stack.isEmpty()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "creating empty delayed merge value"); - - for (com.gu.typesafe.config.impl.AbstractConfigValue v : stack) { - if (v instanceof ConfigDelayedMerge || v instanceof com.gu.typesafe.config.impl.ConfigDelayedMergeObject) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack"); - } - } - - @Override - public ConfigValueType valueType() { - throw new com.gu.typesafe.config.ConfigException.NotResolved( - "called valueType() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); - } - - @Override - public Object unwrapped() { - throw new com.gu.typesafe.config.ConfigException.NotResolved( - "called unwrapped() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); - } - - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) - throws NotPossibleToResolve { - return resolveSubstitutions(this, stack, context, source); - } - - // static method also used by ConfigDelayedMergeObject - static com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ReplaceableMergeStack replaceable, - List stack, - com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) throws NotPossibleToResolve { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) { - com.gu.typesafe.config.impl.ConfigImpl.trace(context.depth(), "delayed merge stack has " + stack.size() + " items:"); - int count = 0; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : stack) { - com.gu.typesafe.config.impl.ConfigImpl.trace(context.depth() + 1, count + ": " + v); - count += 1; - } - } - - // to resolve substitutions, we need to recursively resolve - // the stack of stuff to merge, and merge the stack so - // we won't be a delayed merge anymore. If restrictToChildOrNull - // is non-null, or resolve options allow partial resolves, - // we may remain a delayed merge though. - - com.gu.typesafe.config.impl.ResolveContext newContext = context; - int count = 0; - com.gu.typesafe.config.impl.AbstractConfigValue merged = null; - for (com.gu.typesafe.config.impl.AbstractConfigValue end : stack) { - // the end value may or may not be resolved already - - com.gu.typesafe.config.impl.ResolveSource sourceForEnd; - - if (end instanceof com.gu.typesafe.config.impl.ReplaceableMergeStack) - throw new ConfigException.BugOrBroken("A delayed merge should not contain another one: " + replaceable); - else if (end instanceof com.gu.typesafe.config.impl.Unmergeable) { - // the remainder could be any kind of value, including another - // ConfigDelayedMerge - com.gu.typesafe.config.impl.AbstractConfigValue remainder = replaceable.makeReplacement(context, count + 1); - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "remainder portion: " + remainder); - - // If, while resolving 'end' we come back to the same - // merge stack, we only want to look _below_ 'end' - // in the stack. So we arrange to replace the - // ConfigDelayedMerge with a value that is only - // the remainder of the stack below this one. - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "building sourceForEnd"); - - // we resetParents() here because we'll be resolving "end" - // against a root which does NOT contain "end" - sourceForEnd = source.replaceWithinCurrentParent((com.gu.typesafe.config.impl.AbstractConfigValue) replaceable, remainder); - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), " sourceForEnd before reset parents but after replace: " - + sourceForEnd); - - sourceForEnd = sourceForEnd.resetParents(); - } else { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), - "will resolve end against the original source with parent pushed"); - - sourceForEnd = source.pushParent(replaceable); - } - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) { - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "sourceForEnd =" + sourceForEnd); - } - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "Resolving highest-priority item in delayed merge " + end - + " against " + sourceForEnd + " endWasRemoved=" + (source != sourceForEnd)); - com.gu.typesafe.config.impl.ResolveResult result = newContext.resolve(end, sourceForEnd); - com.gu.typesafe.config.impl.AbstractConfigValue resolvedEnd = result.value; - newContext = result.context; - - if (resolvedEnd != null) { - if (merged == null) { - merged = resolvedEnd; - } else { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd); - merged = merged.withFallback(resolvedEnd); - } - } - - count += 1; - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "stack merged, yielding: " + merged); - } - - return com.gu.typesafe.config.impl.ResolveResult.make(newContext, merged); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue makeReplacement(com.gu.typesafe.config.impl.ResolveContext context, int skipping) { - return ConfigDelayedMerge.makeReplacement(context, stack, skipping); - } - - // static method also used by ConfigDelayedMergeObject; end may be null - static com.gu.typesafe.config.impl.AbstractConfigValue makeReplacement(com.gu.typesafe.config.impl.ResolveContext context, List stack, int skipping) { - List subStack = stack.subList(skipping, stack.size()); - - if (subStack.isEmpty()) { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "Nothing else in the merge stack, replacing with null"); - return null; - } else { - // generate a new merge stack from only the remaining items - com.gu.typesafe.config.impl.AbstractConfigValue merged = null; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : subStack) { - if (merged == null) - merged = v; - else - merged = merged.withFallback(v); - } - return merged; - } - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.UNRESOLVED; - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - List newStack = replaceChildInList(stack, child, replacement); - if (newStack == null) - return null; - else - return new ConfigDelayedMerge(origin(), newStack); - } - - @Override - public boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant) { - return hasDescendantInList(stack, descendant); - } - - @Override - ConfigDelayedMerge relativized(Path prefix) { - List newStack = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigValue o : stack) { - newStack.add(o.relativized(prefix)); - } - return new ConfigDelayedMerge(origin(), newStack); - } - - // static utility shared with ConfigDelayedMergeObject - static boolean stackIgnoresFallbacks(List stack) { - com.gu.typesafe.config.impl.AbstractConfigValue last = stack.get(stack.size() - 1); - return last.ignoresFallbacks(); - } - - @Override - protected boolean ignoresFallbacks() { - return stackIgnoresFallbacks(stack); - } - - @Override - protected com.gu.typesafe.config.impl.AbstractConfigValue newCopy(ConfigOrigin newOrigin) { - return new ConfigDelayedMerge(newOrigin, stack); - } - - @Override - protected final ConfigDelayedMerge mergedWithTheUnmergeable(com.gu.typesafe.config.impl.Unmergeable fallback) { - return (ConfigDelayedMerge) mergedWithTheUnmergeable(stack, fallback); - } - - @Override - protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) { - return (ConfigDelayedMerge) mergedWithObject(stack, fallback); - } - - @Override - protected ConfigDelayedMerge mergedWithNonObject(com.gu.typesafe.config.impl.AbstractConfigValue fallback) { - return (ConfigDelayedMerge) mergedWithNonObject(stack, fallback); - } - - @Override - public Collection unmergedValues() { - return stack; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof ConfigDelayedMerge; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigDelayedMerge) { - return canEqual(other) - && (this.stack == ((ConfigDelayedMerge) other).stack || this.stack - .equals(((ConfigDelayedMerge) other).stack)); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - return stack.hashCode(); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { - render(stack, sb, indent, atRoot, atKey, options); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - render(sb, indent, atRoot, null, options); - } - - // static method also used by ConfigDelayedMergeObject. - static void render(List stack, StringBuilder sb, int indent, boolean atRoot, String atKey, - ConfigRenderOptions options) { - boolean commentMerge = options.getComments(); - if (commentMerge) { - sb.append("# unresolved merge of " + stack.size() + " values follows (\n"); - if (atKey == null) { - indent(sb, indent, options); - sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n"); - indent(sb, indent, options); - sb.append("# the HOCON format has no way to list multiple root objects in a single file\n"); - } - } - - List reversed = new ArrayList(); - reversed.addAll(stack); - Collections.reverse(reversed); - - int i = 0; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : reversed) { - if (commentMerge) { - indent(sb, indent, options); - if (atKey != null) { - sb.append("# unmerged value " + i + " for key " - + com.gu.typesafe.config.impl.ConfigImplUtil.renderJsonString(atKey) + " from "); - } else { - sb.append("# unmerged value " + i + " from "); - } - i += 1; - sb.append(v.origin().description()); - sb.append("\n"); - - for (String comment : v.origin().comments()) { - indent(sb, indent, options); - sb.append("# "); - sb.append(comment); - sb.append("\n"); - } - } - indent(sb, indent, options); - - if (atKey != null) { - sb.append(ConfigImplUtil.renderJsonString(atKey)); - if (options.getFormatted()) - sb.append(" : "); - else - sb.append(":"); - } - v.render(sb, indent, atRoot, options); - sb.append(","); - if (options.getFormatted()) - sb.append('\n'); - } - // chop comma or newline - sb.setLength(sb.length() - 1); - if (options.getFormatted()) { - sb.setLength(sb.length() - 1); // also chop comma - sb.append("\n"); // put a newline back - } - if (commentMerge) { - indent(sb, indent, options); - sb.append("# ) end of unresolved merge\n"); - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMergeObject.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMergeObject.java deleted file mode 100644 index b458ccf208e..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDelayedMergeObject.java +++ /dev/null @@ -1,326 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigList; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValue; -import com.gu.typesafe.config.ConfigMergeable; -import com.gu.typesafe.config.ConfigRenderOptions; - -// This is just like ConfigDelayedMerge except we know statically -// that it will turn out to be an object. -final class ConfigDelayedMergeObject extends AbstractConfigObject implements com.gu.typesafe.config.impl.Unmergeable, - com.gu.typesafe.config.impl.ReplaceableMergeStack { - - final private List stack; - - ConfigDelayedMergeObject(com.gu.typesafe.config.ConfigOrigin origin, List stack) { - super(origin); - this.stack = stack; - - if (stack.isEmpty()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "creating empty delayed merge object"); - if (!(stack.get(0) instanceof AbstractConfigObject)) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "created a delayed merge object not guaranteed to be an object"); - - for (com.gu.typesafe.config.impl.AbstractConfigValue v : stack) { - if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack"); - } - } - - @Override - protected ConfigDelayedMergeObject newCopy(ResolveStatus status, ConfigOrigin origin) { - if (status != resolveStatus()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "attempt to create resolved ConfigDelayedMergeObject"); - return new ConfigDelayedMergeObject(origin, stack); - } - - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) - throws NotPossibleToResolve { - com.gu.typesafe.config.impl.ResolveResult merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, - context, source); - return merged.asObjectResult(); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue makeReplacement(com.gu.typesafe.config.impl.ResolveContext context, int skipping) { - return ConfigDelayedMerge.makeReplacement(context, stack, skipping); - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.UNRESOLVED; - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - List newStack = replaceChildInList(stack, child, replacement); - if (newStack == null) - return null; - else - return new ConfigDelayedMergeObject(origin(), newStack); - } - - @Override - public boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant) { - return hasDescendantInList(stack, descendant); - } - - @Override - ConfigDelayedMergeObject relativized(Path prefix) { - List newStack = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigValue o : stack) { - newStack.add(o.relativized(prefix)); - } - return new ConfigDelayedMergeObject(origin(), newStack); - } - - @Override - protected boolean ignoresFallbacks() { - return ConfigDelayedMerge.stackIgnoresFallbacks(stack); - } - - @Override - protected final ConfigDelayedMergeObject mergedWithTheUnmergeable(com.gu.typesafe.config.impl.Unmergeable fallback) { - requireNotIgnoringFallbacks(); - - return (ConfigDelayedMergeObject) mergedWithTheUnmergeable(stack, fallback); - } - - @Override - protected final ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) { - return mergedWithNonObject(fallback); - } - - @Override - protected final ConfigDelayedMergeObject mergedWithNonObject(com.gu.typesafe.config.impl.AbstractConfigValue fallback) { - requireNotIgnoringFallbacks(); - - return (ConfigDelayedMergeObject) mergedWithNonObject(stack, fallback); - } - - @Override - public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) { - return (ConfigDelayedMergeObject) super.withFallback(mergeable); - } - - @Override - public ConfigDelayedMergeObject withOnlyKey(String key) { - throw notResolved(); - } - - @Override - public ConfigDelayedMergeObject withoutKey(String key) { - throw notResolved(); - } - - @Override - protected AbstractConfigObject withOnlyPathOrNull(Path path) { - throw notResolved(); - } - - @Override - AbstractConfigObject withOnlyPath(Path path) { - throw notResolved(); - } - - @Override - AbstractConfigObject withoutPath(Path path) { - throw notResolved(); - } - - @Override - public ConfigDelayedMergeObject withValue(String key, com.gu.typesafe.config.ConfigValue value) { - throw notResolved(); - } - - @Override - ConfigDelayedMergeObject withValue(Path path, com.gu.typesafe.config.ConfigValue value) { - throw notResolved(); - } - - @Override - public Collection unmergedValues() { - return stack; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof ConfigDelayedMergeObject; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigDelayedMergeObject) { - return canEqual(other) - && (this.stack == ((ConfigDelayedMergeObject) other).stack || this.stack - .equals(((ConfigDelayedMergeObject) other).stack)); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - return stack.hashCode(); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { - ConfigDelayedMerge.render(stack, sb, indent, atRoot, atKey, options); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - render(sb, indent, atRoot, null, options); - } - - private static com.gu.typesafe.config.ConfigException notResolved() { - return new com.gu.typesafe.config.ConfigException.NotResolved( - "need to Config#resolve() before using this object, see the API docs for Config#resolve()"); - } - - @Override - public Map unwrapped() { - throw notResolved(); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue get(Object key) { - throw notResolved(); - } - - @Override - public boolean containsKey(Object key) { - throw notResolved(); - } - - @Override - public boolean containsValue(Object value) { - throw notResolved(); - } - - @Override - public Set> entrySet() { - throw notResolved(); - } - - @Override - public boolean isEmpty() { - throw notResolved(); - } - - @Override - public Set keySet() { - throw notResolved(); - } - - @Override - public int size() { - throw notResolved(); - } - - @Override - public Collection values() { - throw notResolved(); - } - - @Override - protected com.gu.typesafe.config.impl.AbstractConfigValue attemptPeekWithPartialResolve(String key) { - // a partial resolve of a ConfigDelayedMergeObject always results in a - // SimpleConfigObject because all the substitutions in the stack get - // resolved in order to look up the partial. - // So we know here that we have not been resolved at all even - // partially. - // Given that, all this code is probably gratuitous, since the app code - // is likely broken. But in general we only throw NotResolved if you try - // to touch the exact key that isn't resolved, so this is in that - // spirit. - - // we'll be able to return a key if we have a value that ignores - // fallbacks, prior to any unmergeable values. - for (com.gu.typesafe.config.impl.AbstractConfigValue layer : stack) { - if (layer instanceof AbstractConfigObject) { - AbstractConfigObject objectLayer = (AbstractConfigObject) layer; - com.gu.typesafe.config.impl.AbstractConfigValue v = objectLayer.attemptPeekWithPartialResolve(key); - if (v != null) { - if (v.ignoresFallbacks()) { - // we know we won't need to merge anything in to this - // value - return v; - } else { - // we can't return this value because we know there are - // unmergeable values later in the stack that may - // contain values that need to be merged with this - // value. we'll throw the exception when we get to those - // unmergeable values, so continue here. - continue; - } - } else if (layer instanceof com.gu.typesafe.config.impl.Unmergeable) { - // an unmergeable object (which would be another - // ConfigDelayedMergeObject) can't know that a key is - // missing, so it can't return null; it can only return a - // value or throw NotPossibleToResolve - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "should not be reached: unmergeable object returned null value"); - } else { - // a non-unmergeable AbstractConfigObject that returned null - // for the key in question is not relevant, we can keep - // looking for a value. - continue; - } - } else if (layer instanceof com.gu.typesafe.config.impl.Unmergeable) { - throw new com.gu.typesafe.config.ConfigException.NotResolved("Key '" + key + "' is not available at '" - + origin().description() + "' because value at '" - + layer.origin().description() - + "' has not been resolved and may turn out to contain or hide '" + key - + "'." - + " Be sure to Config#resolve() before using a config object."); - } else if (layer.resolveStatus() == ResolveStatus.UNRESOLVED) { - // if the layer is not an object, and not a substitution or - // merge, - // then it's something that's unresolved because it _contains_ - // an unresolved object... i.e. it's an array - if (!(layer instanceof ConfigList)) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Expecting a list here, not " + layer); - // all later objects will be hidden so we can say we won't find - // the key - return null; - } else { - // non-object, but resolved, like an integer or something. - // has no children so the one we're after won't be in it. - // we would only have this in the stack in case something - // else "looks back" to it due to a cycle. - // anyway at this point we know we can't find the key anymore. - if (!layer.ignoresFallbacks()) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "resolved non-object should ignore fallbacks"); - } - return null; - } - } - // If we get here, then we never found anything unresolved which means - // the ConfigDelayedMergeObject should not have existed. some - // invariant was violated. - throw new ConfigException.BugOrBroken( - "Delayed merge stack does not contain any unmergeable values"); - - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDocumentParser.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDocumentParser.java deleted file mode 100644 index c25336ddb09..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDocumentParser.java +++ /dev/null @@ -1,712 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.*; - -import com.gu.typesafe.config.ConfigSyntax; - -final class ConfigDocumentParser { - static com.gu.typesafe.config.impl.ConfigNodeRoot parse(Iterator tokens, com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.ConfigParseOptions options) { - com.gu.typesafe.config.ConfigSyntax syntax = options.getSyntax() == null ? com.gu.typesafe.config.ConfigSyntax.CONF : options.getSyntax(); - ParseContext context = new ParseContext(syntax, origin, tokens); - return context.parse(); - } - - static com.gu.typesafe.config.impl.AbstractConfigNodeValue parseValue(Iterator tokens, com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.ConfigParseOptions options) { - com.gu.typesafe.config.ConfigSyntax syntax = options.getSyntax() == null ? com.gu.typesafe.config.ConfigSyntax.CONF : options.getSyntax(); - ParseContext context = new ParseContext(syntax, origin, tokens); - return context.parseSingleValue(); - } - - static private final class ParseContext { - private int lineNumber; - final private Stack buffer; - final private Iterator tokens; - final private com.gu.typesafe.config.ConfigSyntax flavor; - final private com.gu.typesafe.config.ConfigOrigin baseOrigin; - // this is the number of "equals" we are inside, - // used to modify the error message to reflect that - // someone may think this is .properties format. - int equalsCount; - - ParseContext(com.gu.typesafe.config.ConfigSyntax flavor, com.gu.typesafe.config.ConfigOrigin origin, Iterator tokens) { - lineNumber = 1; - buffer = new Stack(); - this.tokens = tokens; - this.flavor = flavor; - this.equalsCount = 0; - this.baseOrigin = origin; - } - - private com.gu.typesafe.config.impl.Token popToken() { - if (buffer.isEmpty()) { - return tokens.next(); - } - return buffer.pop(); - } - - private com.gu.typesafe.config.impl.Token nextToken() { - com.gu.typesafe.config.impl.Token t = popToken(); - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) && !isUnquotedWhitespace(t)) { - throw parseError("Token not allowed in valid JSON: '" - + com.gu.typesafe.config.impl.Tokens.getUnquotedText(t) + "'"); - } else if (com.gu.typesafe.config.impl.Tokens.isSubstitution(t)) { - throw parseError("Substitutions (${} syntax) not allowed in JSON"); - } - } - return t; - } - - private com.gu.typesafe.config.impl.Token nextTokenCollectingWhitespace(Collection nodes) { - while (true) { - com.gu.typesafe.config.impl.Token t = nextToken(); - if (com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(t) || com.gu.typesafe.config.impl.Tokens.isNewline(t) || isUnquotedWhitespace(t)) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - if (com.gu.typesafe.config.impl.Tokens.isNewline(t)) { - lineNumber = t.lineNumber() + 1; - } - } else if (com.gu.typesafe.config.impl.Tokens.isComment(t)) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeComment(t)); - } else { - int newNumber = t.lineNumber(); - if (newNumber >= 0) - lineNumber = newNumber; - return t; - } - } - } - - private void putBack(com.gu.typesafe.config.impl.Token token) { - buffer.push(token); - } - - // In arrays and objects, comma can be omitted - // as long as there's at least one newline instead. - // this skips any newlines in front of a comma, - // skips the comma, and returns true if it found - // either a newline or a comma. The iterator - // is left just after the comma or the newline. - private boolean checkElementSeparator(Collection nodes) { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - com.gu.typesafe.config.impl.Token t = nextTokenCollectingWhitespace(nodes); - if (t == com.gu.typesafe.config.impl.Tokens.COMMA) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - return true; - } else { - putBack(t); - return false; - } - } else { - boolean sawSeparatorOrNewline = false; - com.gu.typesafe.config.impl.Token t = nextToken(); - while (true) { - if (com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(t) || isUnquotedWhitespace(t)) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - } else if (com.gu.typesafe.config.impl.Tokens.isComment(t)) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeComment(t)); - } else if (com.gu.typesafe.config.impl.Tokens.isNewline(t)) { - sawSeparatorOrNewline = true; - lineNumber++; - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - // we want to continue to also eat - // a comma if there is one. - } else if (t == com.gu.typesafe.config.impl.Tokens.COMMA) { - nodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - return true; - } else { - // non-newline-or-comma - putBack(t); - return sawSeparatorOrNewline; - } - t = nextToken(); - } - } - } - - // parse a concatenation. If there is no concatenation, return the next value - private com.gu.typesafe.config.impl.AbstractConfigNodeValue consolidateValues(Collection nodes) { - // this trick is not done in JSON - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) - return null; - - // create only if we have value tokens - ArrayList values = new ArrayList(); - int valueCount = 0; - - // ignore a newline up front - com.gu.typesafe.config.impl.Token t = nextTokenCollectingWhitespace(nodes); - while (true) { - com.gu.typesafe.config.impl.AbstractConfigNodeValue v = null; - if (com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(t)) { - values.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - t = nextToken(); - continue; - } - else if (com.gu.typesafe.config.impl.Tokens.isValue(t) || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) - || com.gu.typesafe.config.impl.Tokens.isSubstitution(t) || t == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY - || t == com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE) { - // there may be newlines _within_ the objects and arrays - v = parseValue(t); - valueCount++; - } else { - break; - } - - if (v == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("no value"); - - values.add(v); - - t = nextToken(); // but don't consolidate across a newline - } - - putBack(t); - - // No concatenation was seen, but a single value may have been parsed, so return it, and put back - // all succeeding tokens - if (valueCount < 2) { - com.gu.typesafe.config.impl.AbstractConfigNodeValue value = null; - for (com.gu.typesafe.config.impl.AbstractConfigNode node : values) { - if (node instanceof com.gu.typesafe.config.impl.AbstractConfigNodeValue) - value = (com.gu.typesafe.config.impl.AbstractConfigNodeValue)node; - else if (value == null) - nodes.add(node); - else - putBack((new ArrayList(node.tokens())).get(0)); - } - return value; - } - - // Put back any trailing whitespace, as the parent object is responsible for tracking - // any leading/trailing whitespace - for (int i = values.size() - 1; i >= 0; i--) { - if (values.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - putBack(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) values.get(i)).token()); - values.remove(i); - } else { - break; - } - } - return new com.gu.typesafe.config.impl.ConfigNodeConcatenation(values); - } - - private com.gu.typesafe.config.ConfigException parseError(String message) { - return parseError(message, null); - } - - private com.gu.typesafe.config.ConfigException parseError(String message, Throwable cause) { - return new com.gu.typesafe.config.ConfigException.Parse(baseOrigin.withLineNumber(lineNumber), message, cause); - } - - private String addQuoteSuggestion(String badToken, String message) { - return addQuoteSuggestion(null, equalsCount > 0, badToken, message); - } - - private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken, - String message) { - String previousFieldName = lastPath != null ? lastPath.render() : null; - - String part; - if (badToken.equals(com.gu.typesafe.config.impl.Tokens.END.toString())) { - // EOF requires special handling for the error to make sense. - if (previousFieldName != null) - part = message + " (if you intended '" + previousFieldName - + "' to be part of a value, instead of a key, " - + "try adding double quotes around the whole value"; - else - return message; - } else { - if (previousFieldName != null) { - part = message + " (if you intended " + badToken - + " to be part of the value for '" + previousFieldName + "', " - + "try enclosing the value in double quotes"; - } else { - part = message + " (if you intended " + badToken - + " to be part of a key or string value, " - + "try enclosing the key or value in double quotes"; - } - } - - if (insideEquals) - return part - + ", or you may be able to rename the file .properties rather than .conf)"; - else - return part + ")"; - } - - private com.gu.typesafe.config.impl.AbstractConfigNodeValue parseValue(com.gu.typesafe.config.impl.Token t) { - com.gu.typesafe.config.impl.AbstractConfigNodeValue v = null; - int startingEqualsCount = equalsCount; - - if (com.gu.typesafe.config.impl.Tokens.isValue(t) || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) || com.gu.typesafe.config.impl.Tokens.isSubstitution(t)) { - v = new com.gu.typesafe.config.impl.ConfigNodeSimpleValue(t); - } else if (t == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY) { - v = parseObject(true); - } else if (t== com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE) { - v = parseArray(); - } else { - throw parseError(addQuoteSuggestion(t.toString(), - "Expecting a value but got wrong token: " + t)); - } - - if (equalsCount != startingEqualsCount) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Bug in config parser: unbalanced equals count"); - - return v; - } - - private com.gu.typesafe.config.impl.ConfigNodePath parseKey(com.gu.typesafe.config.impl.Token token) { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - if (com.gu.typesafe.config.impl.Tokens.isValueWithType(token, com.gu.typesafe.config.ConfigValueType.STRING)) { - return com.gu.typesafe.config.impl.PathParser.parsePathNodeExpression(Collections.singletonList(token).iterator(), - baseOrigin.withLineNumber(lineNumber)); - } else { - throw parseError("Expecting close brace } or a field name here, got " - + token); - } - } else { - List expression = new ArrayList(); - com.gu.typesafe.config.impl.Token t = token; - while (com.gu.typesafe.config.impl.Tokens.isValue(t) || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t)) { - expression.add(t); - t = nextToken(); // note: don't cross a newline - } - - if (expression.isEmpty()) { - throw parseError(ExpectingClosingParenthesisError + t); - } - - putBack(t); // put back the token we ended with - return com.gu.typesafe.config.impl.PathParser.parsePathNodeExpression(expression.iterator(), - baseOrigin.withLineNumber(lineNumber)); - } - } - - private static boolean isIncludeKeyword(com.gu.typesafe.config.impl.Token t) { - return com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) - && com.gu.typesafe.config.impl.Tokens.getUnquotedText(t).equals("include"); - } - - private static boolean isUnquotedWhitespace(com.gu.typesafe.config.impl.Token t) { - if (!com.gu.typesafe.config.impl.Tokens.isUnquotedText(t)) - return false; - - String s = com.gu.typesafe.config.impl.Tokens.getUnquotedText(t); - - for (int i = 0; i < s.length(); ++i) { - char c = s.charAt(i); - if (!ConfigImplUtil.isWhitespace(c)) - return false; - } - return true; - } - - private boolean isKeyValueSeparatorToken(com.gu.typesafe.config.impl.Token t) { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - return t == com.gu.typesafe.config.impl.Tokens.COLON; - } else { - return t == com.gu.typesafe.config.impl.Tokens.COLON || t == com.gu.typesafe.config.impl.Tokens.EQUALS || t == com.gu.typesafe.config.impl.Tokens.PLUS_EQUALS; - } - } - - private final String ExpectingClosingParenthesisError = "expecting a close parentheses ')' here, not: "; - - private com.gu.typesafe.config.impl.ConfigNodeInclude parseInclude(ArrayList children) { - - com.gu.typesafe.config.impl.Token t = nextTokenCollectingWhitespace(children); - - // we either have a 'required()' or a quoted string or the "file()" syntax - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(t)) { - String kindText = com.gu.typesafe.config.impl.Tokens.getUnquotedText(t); - - if (kindText.startsWith("required(")) { - String r = kindText.replaceFirst("required\\(",""); - if (r.length()>0) { - putBack(com.gu.typesafe.config.impl.Tokens.newUnquotedText(t.origin(),r)); - } - - children.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - //children.add(new ConfigNodeSingleToken(tOpen)); - - com.gu.typesafe.config.impl.ConfigNodeInclude res = parseIncludeResource(children, true); - - t = nextTokenCollectingWhitespace(children); - - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) && com.gu.typesafe.config.impl.Tokens.getUnquotedText(t).equals(")")) { - // OK, close paren - } else { - throw parseError(ExpectingClosingParenthesisError + t); - } - - return res; - } else { - putBack(t); - return parseIncludeResource(children, false); - } - } - else { - putBack(t); - return parseIncludeResource(children, false); - } - } - - private com.gu.typesafe.config.impl.ConfigNodeInclude parseIncludeResource(ArrayList children, boolean isRequired) { - com.gu.typesafe.config.impl.Token t = nextTokenCollectingWhitespace(children); - - // we either have a quoted string or the "file()" syntax - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(t)) { - // get foo( - String kindText = com.gu.typesafe.config.impl.Tokens.getUnquotedText(t); - com.gu.typesafe.config.impl.ConfigIncludeKind kind; - String prefix; - - if (kindText.startsWith("url(")) { - kind = com.gu.typesafe.config.impl.ConfigIncludeKind.URL; - prefix = "url("; - } else if (kindText.startsWith("file(")) { - kind = com.gu.typesafe.config.impl.ConfigIncludeKind.FILE; - prefix = "file("; - } else if (kindText.startsWith("classpath(")) { - kind = com.gu.typesafe.config.impl.ConfigIncludeKind.CLASSPATH; - prefix = "classpath("; - } else { - throw parseError("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: " - + t); - } - String r = kindText.replaceFirst("[^(]*\\(",""); - if (r.length()>0) { - putBack(com.gu.typesafe.config.impl.Tokens.newUnquotedText(t.origin(),r)); - } - - children.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - - // skip space inside parens - t = nextTokenCollectingWhitespace(children); - - // quoted string - if (!com.gu.typesafe.config.impl.Tokens.isValueWithType(t, com.gu.typesafe.config.ConfigValueType.STRING)) { - throw parseError("expecting include " + prefix + ") parameter to be a quoted string, rather than: " + t); - } - children.add(new com.gu.typesafe.config.impl.ConfigNodeSimpleValue(t)); - // skip space after string, inside parens - t = nextTokenCollectingWhitespace(children); - - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) && com.gu.typesafe.config.impl.Tokens.getUnquotedText(t).startsWith(")")) { - String rest = com.gu.typesafe.config.impl.Tokens.getUnquotedText(t).substring(1); - if (rest.length()>0) { - putBack(com.gu.typesafe.config.impl.Tokens.newUnquotedText(t.origin(),rest)); - } - // OK, close paren - } else { - throw parseError(ExpectingClosingParenthesisError + t); - } - - return new com.gu.typesafe.config.impl.ConfigNodeInclude(children, kind, isRequired); - } else if (com.gu.typesafe.config.impl.Tokens.isValueWithType(t, com.gu.typesafe.config.ConfigValueType.STRING)) { - children.add(new com.gu.typesafe.config.impl.ConfigNodeSimpleValue(t)); - return new com.gu.typesafe.config.impl.ConfigNodeInclude(children, com.gu.typesafe.config.impl.ConfigIncludeKind.HEURISTIC, isRequired); - } else { - throw parseError("include keyword is not followed by a quoted string, but by: " + t); - } - } - - private com.gu.typesafe.config.impl.ConfigNodeComplexValue parseObject(boolean hadOpenCurly) { - // invoked just after the OPEN_CURLY (or START, if !hadOpenCurly) - boolean afterComma = false; - Path lastPath = null; - boolean lastInsideEquals = false; - ArrayList objectNodes = new ArrayList(); - ArrayList keyValueNodes; - HashMap keys = new HashMap(); - if (hadOpenCurly) - objectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.OPEN_CURLY)); - - while (true) { - com.gu.typesafe.config.impl.Token t = nextTokenCollectingWhitespace(objectNodes); - if (t == com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY) { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON && afterComma) { - throw parseError(addQuoteSuggestion(t.toString(), - "expecting a field name after a comma, got a close brace } instead")); - } else if (!hadOpenCurly) { - throw parseError(addQuoteSuggestion(t.toString(), - "unbalanced close brace '}' with no open brace")); - } - objectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY)); - break; - } else if (t == com.gu.typesafe.config.impl.Tokens.END && !hadOpenCurly) { - putBack(t); - break; - } else if (flavor != com.gu.typesafe.config.ConfigSyntax.JSON && isIncludeKeyword(t)) { - ArrayList includeNodes = new ArrayList(); - includeNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - objectNodes.add(parseInclude(includeNodes)); - afterComma = false; - } else { - keyValueNodes = new ArrayList(); - com.gu.typesafe.config.impl.Token keyToken = t; - com.gu.typesafe.config.impl.ConfigNodePath path = parseKey(keyToken); - keyValueNodes.add(path); - com.gu.typesafe.config.impl.Token afterKey = nextTokenCollectingWhitespace(keyValueNodes); - boolean insideEquals = false; - - com.gu.typesafe.config.impl.AbstractConfigNodeValue nextValue; - if (flavor == com.gu.typesafe.config.ConfigSyntax.CONF && afterKey == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY) { - // can omit the ':' or '=' before an object value - nextValue = parseValue(afterKey); - } else { - if (!isKeyValueSeparatorToken(afterKey)) { - throw parseError(addQuoteSuggestion(afterKey.toString(), - "Key '" + path.render() + "' may not be followed by token: " - + afterKey)); - } - - keyValueNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(afterKey)); - - if (afterKey == com.gu.typesafe.config.impl.Tokens.EQUALS) { - insideEquals = true; - equalsCount += 1; - } - - nextValue = consolidateValues(keyValueNodes); - if (nextValue == null) { - nextValue = parseValue(nextTokenCollectingWhitespace(keyValueNodes)); - } - } - - keyValueNodes.add(nextValue); - if (insideEquals) { - equalsCount -= 1; - } - lastInsideEquals = insideEquals; - - String key = path.value().first(); - Path remaining = path.value().remainder(); - - if (remaining == null) { - Boolean existing = keys.get(key); - if (existing != null) { - // In strict JSON, dups should be an error; while in - // our custom config language, they should be merged - // if the value is an object (or substitution that - // could become an object). - - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - throw parseError("JSON does not allow duplicate fields: '" - + key - + "' was already seen"); - } - } - keys.put(key, true); - } else { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "somehow got multi-element path in JSON mode"); - } - keys.put(key, true); - } - - afterComma = false; - objectNodes.add(new ConfigNodeField(keyValueNodes)); - } - - if (checkElementSeparator(objectNodes)) { - // continue looping - afterComma = true; - } else { - t = nextTokenCollectingWhitespace(objectNodes); - if (t == com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY) { - if (!hadOpenCurly) { - throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, - t.toString(), "unbalanced close brace '}' with no open brace")); - } - objectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - break; - } else if (hadOpenCurly) { - throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, - t.toString(), "Expecting close brace } or a comma, got " + t)); - } else { - if (t == com.gu.typesafe.config.impl.Tokens.END) { - putBack(t); - break; - } else { - throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, - t.toString(), "Expecting end of input or a comma, got " + t)); - } - } - } - } - - return new com.gu.typesafe.config.impl.ConfigNodeObject(objectNodes); - } - - private com.gu.typesafe.config.impl.ConfigNodeComplexValue parseArray() { - ArrayList children = new ArrayList(); - children.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE)); - // invoked just after the OPEN_SQUARE - com.gu.typesafe.config.impl.Token t; - - com.gu.typesafe.config.impl.AbstractConfigNodeValue nextValue = consolidateValues(children); - if (nextValue != null) { - children.add(nextValue); - } else { - t = nextTokenCollectingWhitespace(children); - - // special-case the first element - if (t == com.gu.typesafe.config.impl.Tokens.CLOSE_SQUARE) { - children.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - return new ConfigNodeArray(children); - } else if (com.gu.typesafe.config.impl.Tokens.isValue(t) || t == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY - || t == com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) - || com.gu.typesafe.config.impl.Tokens.isSubstitution(t)) { - nextValue = parseValue(t); - children.add(nextValue); - } else { - throw parseError("List should have ] or a first element after the open [, instead had token: " - + t - + " (if you want " - + t - + " to be part of a string value, then double-quote it)"); - } - } - - // now remaining elements - while (true) { - // just after a value - if (checkElementSeparator(children)) { - // comma (or newline equivalent) consumed - } else { - t = nextTokenCollectingWhitespace(children); - if (t == com.gu.typesafe.config.impl.Tokens.CLOSE_SQUARE) { - children.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(t)); - return new ConfigNodeArray(children); - } else { - throw parseError("List should have ended with ] or had a comma, instead had token: " - + t - + " (if you want " - + t - + " to be part of a string value, then double-quote it)"); - } - } - - // now just after a comma - nextValue = consolidateValues(children); - if (nextValue != null) { - children.add(nextValue); - } else { - t = nextTokenCollectingWhitespace(children); - if (com.gu.typesafe.config.impl.Tokens.isValue(t) || t == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY - || t == com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) - || com.gu.typesafe.config.impl.Tokens.isSubstitution(t)) { - nextValue = parseValue(t); - children.add(nextValue); - } else if (flavor != com.gu.typesafe.config.ConfigSyntax.JSON && t == com.gu.typesafe.config.impl.Tokens.CLOSE_SQUARE) { - // we allow one trailing comma - putBack(t); - } else { - throw parseError("List should have had new element after a comma, instead had token: " - + t - + " (if you want the comma or " - + t - + " to be part of a string value, then double-quote it)"); - } - } - } - } - - com.gu.typesafe.config.impl.ConfigNodeRoot parse() { - ArrayList children = new ArrayList(); - com.gu.typesafe.config.impl.Token t = nextToken(); - if (t == com.gu.typesafe.config.impl.Tokens.START) { - // OK - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "token stream did not begin with START, had " + t); - } - - t = nextTokenCollectingWhitespace(children); - com.gu.typesafe.config.impl.AbstractConfigNode result = null; - boolean missingCurly = false; - if (t == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY || t == com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE) { - result = parseValue(t); - } else { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - if (t == com.gu.typesafe.config.impl.Tokens.END) { - throw parseError("Empty document"); - } else { - throw parseError("Document must have an object or array at root, unexpected token: " - + t); - } - } else { - // the root object can omit the surrounding braces. - // this token should be the first field's key, or part - // of it, so put it back. - putBack(t); - missingCurly = true; - result = parseObject(false); - } - } - // Need to pull the children out of the resulting node so we can keep leading - // and trailing whitespace if this was a no-brace object. Otherwise, we need to add - // the result into the list of children. - if (result instanceof com.gu.typesafe.config.impl.ConfigNodeObject && missingCurly) { - children.addAll(((com.gu.typesafe.config.impl.ConfigNodeComplexValue) result).children()); - } else { - children.add(result); - } - t = nextTokenCollectingWhitespace(children); - if (t == com.gu.typesafe.config.impl.Tokens.END) { - if (missingCurly) { - // If there were no braces, the entire document should be treated as a single object - return new com.gu.typesafe.config.impl.ConfigNodeRoot(Collections.singletonList((com.gu.typesafe.config.impl.AbstractConfigNode)new com.gu.typesafe.config.impl.ConfigNodeObject(children)), baseOrigin); - } else { - return new com.gu.typesafe.config.impl.ConfigNodeRoot(children, baseOrigin); - } - } else { - throw parseError("Document has trailing tokens after first object or array: " - + t); - } - } - - // Parse a given input stream into a single value node. Used when doing a replace inside a ConfigDocument. - com.gu.typesafe.config.impl.AbstractConfigNodeValue parseSingleValue() { - com.gu.typesafe.config.impl.Token t = nextToken(); - if (t == com.gu.typesafe.config.impl.Tokens.START) { - // OK - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "token stream did not begin with START, had " + t); - } - - t = nextToken(); - if (com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(t) || com.gu.typesafe.config.impl.Tokens.isNewline(t) || isUnquotedWhitespace(t) || com.gu.typesafe.config.impl.Tokens.isComment(t)) { - throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); - } - if (t == com.gu.typesafe.config.impl.Tokens.END) { - throw parseError("Empty value"); - } - if (flavor == ConfigSyntax.JSON) { - com.gu.typesafe.config.impl.AbstractConfigNodeValue node = parseValue(t); - t = nextToken(); - if (t == com.gu.typesafe.config.impl.Tokens.END) { - return node; - } else { - throw parseError("Parsing JSON and the value set in withValueText was either a concatenation or " + - "had trailing whitespace, newlines, or comments"); - } - } else { - putBack(t); - ArrayList nodes = new ArrayList(); - com.gu.typesafe.config.impl.AbstractConfigNodeValue node = consolidateValues(nodes); - t = nextToken(); - if (t == com.gu.typesafe.config.impl.Tokens.END) { - return node; - } else { - throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); - } - } - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDouble.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDouble.java deleted file mode 100644 index 4438fb3e581..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigDouble.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; - -final class ConfigDouble extends ConfigNumber implements Serializable { - - private static final long serialVersionUID = 2L; - - final private double value; - - ConfigDouble(com.gu.typesafe.config.ConfigOrigin origin, double value, String originalText) { - super(origin, originalText); - this.value = value; - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.NUMBER; - } - - @Override - public Double unwrapped() { - return value; - } - - @Override - String transformToString() { - String s = super.transformToString(); - if (s == null) - return Double.toString(value); - else - return s; - } - - @Override - protected long longValue() { - return (long) value; - } - - @Override - protected double doubleValue() { - return value; - } - - @Override - protected ConfigDouble newCopy(ConfigOrigin origin) { - return new ConfigDouble(origin, value, originalText); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImpl.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImpl.java deleted file mode 100644 index 9770b3ed54a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImpl.java +++ /dev/null @@ -1,539 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.net.URL; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.Callable; - -import com.gu.typesafe.config.*; -import com.gu.typesafe.config.ConfigMemorySize; - -/** - * Internal implementation detail, not ABI stable, do not touch. - * For use only by the {@link com.typesafe.config} package. - */ -public class ConfigImpl { - private static final String ENV_VAR_OVERRIDE_PREFIX = "CONFIG_FORCE_"; - - private static class LoaderCache { - private com.gu.typesafe.config.Config currentSystemProperties; - private WeakReference currentLoader; - private Map cache; - - LoaderCache() { - this.currentSystemProperties = null; - this.currentLoader = new WeakReference(null); - this.cache = new HashMap(); - } - - // for now, caching as long as the loader remains the same, - // drop entire cache if it changes. - synchronized com.gu.typesafe.config.Config getOrElseUpdate(ClassLoader loader, String key, Callable updater) { - if (loader != currentLoader.get()) { - // reset the cache if we start using a different loader - cache.clear(); - currentLoader = new WeakReference(loader); - } - - com.gu.typesafe.config.Config systemProperties = systemPropertiesAsConfig(); - if (systemProperties != currentSystemProperties) { - cache.clear(); - currentSystemProperties = systemProperties; - } - - com.gu.typesafe.config.Config config = cache.get(key); - if (config == null) { - try { - config = updater.call(); - } catch (RuntimeException e) { - throw e; // this will include ConfigException - } catch (Exception e) { - throw new com.gu.typesafe.config.ConfigException.Generic(e.getMessage(), e); - } - if (config == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("null config from cache updater"); - cache.put(key, config); - } - - return config; - } - } - - private static class LoaderCacheHolder { - static final LoaderCache cache = new LoaderCache(); - } - - public static com.gu.typesafe.config.Config computeCachedConfig(ClassLoader loader, String key, - Callable updater) { - LoaderCache cache; - try { - cache = LoaderCacheHolder.cache; - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - return cache.getOrElseUpdate(loader, key, updater); - } - - - static class FileNameSource implements SimpleIncluder.NameSource { - @Override - public com.gu.typesafe.config.ConfigParseable nameToParseable(String name, com.gu.typesafe.config.ConfigParseOptions parseOptions) { - return com.gu.typesafe.config.impl.Parseable.newFile(new File(name), parseOptions); - } - }; - - static class ClasspathNameSource implements SimpleIncluder.NameSource { - @Override - public com.gu.typesafe.config.ConfigParseable nameToParseable(String name, com.gu.typesafe.config.ConfigParseOptions parseOptions) { - return com.gu.typesafe.config.impl.Parseable.newResources(name, parseOptions); - } - }; - - static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource { - final private Class klass; - - public ClasspathNameSourceWithClass(Class klass) { - this.klass = klass; - } - - @Override - public ConfigParseable nameToParseable(String name, com.gu.typesafe.config.ConfigParseOptions parseOptions) { - return com.gu.typesafe.config.impl.Parseable.newResources(klass, name, parseOptions); - } - }; - - public static com.gu.typesafe.config.ConfigObject parseResourcesAnySyntax(Class klass, String resourceBasename, - com.gu.typesafe.config.ConfigParseOptions baseOptions) { - SimpleIncluder.NameSource source = new ClasspathNameSourceWithClass(klass); - return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); - } - - public static com.gu.typesafe.config.ConfigObject parseResourcesAnySyntax(String resourceBasename, - com.gu.typesafe.config.ConfigParseOptions baseOptions) { - SimpleIncluder.NameSource source = new ClasspathNameSource(); - return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); - } - - public static com.gu.typesafe.config.ConfigObject parseFileAnySyntax(File basename, com.gu.typesafe.config.ConfigParseOptions baseOptions) { - SimpleIncluder.NameSource source = new FileNameSource(); - return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions); - } - - static AbstractConfigObject emptyObject(String originDescription) { - com.gu.typesafe.config.ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin - .newSimple(originDescription) : null; - return emptyObject(origin); - } - - public static com.gu.typesafe.config.Config emptyConfig(String originDescription) { - return emptyObject(originDescription).toConfig(); - } - - static AbstractConfigObject empty(com.gu.typesafe.config.ConfigOrigin origin) { - return emptyObject(origin); - } - - // default origin for values created with fromAnyRef and no origin specified - final private static com.gu.typesafe.config.ConfigOrigin defaultValueOrigin = SimpleConfigOrigin - .newSimple("hardcoded value"); - final private static ConfigBoolean defaultTrueValue = new ConfigBoolean( - defaultValueOrigin, true); - final private static ConfigBoolean defaultFalseValue = new ConfigBoolean( - defaultValueOrigin, false); - final private static com.gu.typesafe.config.impl.ConfigNull defaultNullValue = new com.gu.typesafe.config.impl.ConfigNull( - defaultValueOrigin); - final private static SimpleConfigList defaultEmptyList = new SimpleConfigList( - defaultValueOrigin, Collections. emptyList()); - final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject - .empty(defaultValueOrigin); - - private static SimpleConfigList emptyList(com.gu.typesafe.config.ConfigOrigin origin) { - if (origin == null || origin == defaultValueOrigin) - return defaultEmptyList; - else - return new SimpleConfigList(origin, - Collections. emptyList()); - } - - private static AbstractConfigObject emptyObject(com.gu.typesafe.config.ConfigOrigin origin) { - // we want null origin to go to SimpleConfigObject.empty() to get the - // origin "empty config" rather than "hardcoded value" - if (origin == defaultValueOrigin) - return defaultEmptyObject; - else - return SimpleConfigObject.empty(origin); - } - - private static com.gu.typesafe.config.ConfigOrigin valueOrigin(String originDescription) { - if (originDescription == null) - return defaultValueOrigin; - else - return SimpleConfigOrigin.newSimple(originDescription); - } - - public static ConfigValue fromAnyRef(Object object, String originDescription) { - com.gu.typesafe.config.ConfigOrigin origin = valueOrigin(originDescription); - return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS); - } - - public static com.gu.typesafe.config.ConfigObject fromPathMap( - Map pathMap, String originDescription) { - com.gu.typesafe.config.ConfigOrigin origin = valueOrigin(originDescription); - return (ConfigObject) fromAnyRef(pathMap, origin, - FromMapMode.KEYS_ARE_PATHS); - } - - static com.gu.typesafe.config.impl.AbstractConfigValue fromAnyRef(Object object, com.gu.typesafe.config.ConfigOrigin origin, - FromMapMode mapMode) { - if (origin == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "origin not supposed to be null"); - - if (object == null) { - if (origin != defaultValueOrigin) - return new com.gu.typesafe.config.impl.ConfigNull(origin); - else - return defaultNullValue; - } else if(object instanceof com.gu.typesafe.config.impl.AbstractConfigValue) { - return (com.gu.typesafe.config.impl.AbstractConfigValue) object; - } else if (object instanceof Boolean) { - if (origin != defaultValueOrigin) { - return new ConfigBoolean(origin, (Boolean) object); - } else if ((Boolean) object) { - return defaultTrueValue; - } else { - return defaultFalseValue; - } - } else if (object instanceof String) { - return new ConfigString.Quoted(origin, (String) object); - } else if (object instanceof Number) { - // here we always keep the same type that was passed to us, - // rather than figuring out if a Long would fit in an Int - // or a Double has no fractional part. i.e. deliberately - // not using ConfigNumber.newNumber() when we have a - // Double, Integer, or Long. - if (object instanceof Double) { - return new com.gu.typesafe.config.impl.ConfigDouble(origin, (Double) object, null); - } else if (object instanceof Integer) { - return new com.gu.typesafe.config.impl.ConfigInt(origin, (Integer) object, null); - } else if (object instanceof Long) { - return new ConfigLong(origin, (Long) object, null); - } else { - return com.gu.typesafe.config.impl.ConfigNumber.newNumber(origin, - ((Number) object).doubleValue(), null); - } - } else if (object instanceof Duration) { - return new ConfigLong(origin, ((Duration) object).toMillis(), null); - } else if (object instanceof Map) { - if (((Map) object).isEmpty()) - return emptyObject(origin); - - if (mapMode == FromMapMode.KEYS_ARE_KEYS) { - Map values = new HashMap(); - for (Map.Entry entry : ((Map) object).entrySet()) { - Object key = entry.getKey(); - if (!(key instanceof String)) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "bug in method caller: not valid to create ConfigObject from map with non-String key: " - + key); - com.gu.typesafe.config.impl.AbstractConfigValue value = fromAnyRef(entry.getValue(), - origin, mapMode); - values.put((String) key, value); - } - - return new SimpleConfigObject(origin, values); - } else { - return PropertiesParser.fromPathMap(origin, (Map) object); - } - } else if (object instanceof Iterable) { - Iterator i = ((Iterable) object).iterator(); - if (!i.hasNext()) - return emptyList(origin); - - List values = new ArrayList(); - while (i.hasNext()) { - com.gu.typesafe.config.impl.AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode); - values.add(v); - } - - return new SimpleConfigList(origin, values); - } else if (object instanceof ConfigMemorySize) { - return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "bug in method caller: not valid to create ConfigValue from: " - + object); - } - } - - private static class DefaultIncluderHolder { - static final com.gu.typesafe.config.ConfigIncluder defaultIncluder = new SimpleIncluder(null); - } - - static ConfigIncluder defaultIncluder() { - try { - return DefaultIncluderHolder.defaultIncluder; - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - private static Properties getSystemProperties() { - // Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties - final Properties systemProperties = System.getProperties(); - final Properties systemPropertiesCopy = new Properties(); - synchronized (systemProperties) { - for (Map.Entry entry: systemProperties.entrySet()) { - // Java 11 introduces 'java.version.date', but we don't want that to - // overwrite 'java.version' - if (!entry.getKey().toString().startsWith("java.version.")) { - systemPropertiesCopy.put(entry.getKey(), entry.getValue()); - } - } - } - return systemPropertiesCopy; - } - - private static AbstractConfigObject loadSystemProperties() { - return (AbstractConfigObject) com.gu.typesafe.config.impl.Parseable.newProperties(getSystemProperties(), - com.gu.typesafe.config.ConfigParseOptions.defaults().setOriginDescription("system properties")).parse(); - } - - private static class SystemPropertiesHolder { - // this isn't final due to the reloadSystemPropertiesConfig() hack below - static volatile AbstractConfigObject systemProperties = loadSystemProperties(); - } - - static AbstractConfigObject systemPropertiesAsConfigObject() { - try { - return SystemPropertiesHolder.systemProperties; - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - public static com.gu.typesafe.config.Config systemPropertiesAsConfig() { - return systemPropertiesAsConfigObject().toConfig(); - } - - public static void reloadSystemPropertiesConfig() { - // ConfigFactory.invalidateCaches() relies on this having the side - // effect that it drops all caches - SystemPropertiesHolder.systemProperties = loadSystemProperties(); - } - - private static AbstractConfigObject loadEnvVariables() { - return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); - } - - private static class EnvVariablesHolder { - static volatile AbstractConfigObject envVariables = loadEnvVariables(); - } - - static AbstractConfigObject envVariablesAsConfigObject() { - try { - return EnvVariablesHolder.envVariables; - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - public static com.gu.typesafe.config.Config envVariablesAsConfig() { - return envVariablesAsConfigObject().toConfig(); - } - - public static void reloadEnvVariablesConfig() { - // ConfigFactory.invalidateCaches() relies on this having the side - // effect that it drops all caches - EnvVariablesHolder.envVariables = loadEnvVariables(); - } - - - - private static AbstractConfigObject loadEnvVariablesOverrides() { - Map env = new HashMap(System.getenv()); - Map result = new HashMap(); - - for (String key : env.keySet()) { - if (key.startsWith(ENV_VAR_OVERRIDE_PREFIX)) { - result.put(ConfigImplUtil.envVariableAsProperty(key, ENV_VAR_OVERRIDE_PREFIX), env.get(key)); - } - } - - return PropertiesParser.fromStringMap(newSimpleOrigin("env variables overrides"), result); - } - - private static class EnvVariablesOverridesHolder { - static volatile AbstractConfigObject envVariables = loadEnvVariablesOverrides(); - } - - static AbstractConfigObject envVariablesOverridesAsConfigObject() { - try { - return EnvVariablesOverridesHolder.envVariables; - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - public static com.gu.typesafe.config.Config envVariablesOverridesAsConfig() { - return envVariablesOverridesAsConfigObject().toConfig(); - } - - public static void reloadEnvVariablesOverridesConfig() { - // ConfigFactory.invalidateCaches() relies on this having the side - // effect that it drops all caches - EnvVariablesOverridesHolder.envVariables = loadEnvVariablesOverrides(); - } - - public static com.gu.typesafe.config.Config defaultReference(final ClassLoader loader) { - return computeCachedConfig(loader, "defaultReference", new Callable() { - @Override - public com.gu.typesafe.config.Config call() { - com.gu.typesafe.config.Config unresolvedResources = unresolvedReference(loader); - return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve(); - } - }); - } - - private static com.gu.typesafe.config.Config unresolvedReference(final ClassLoader loader) { - return computeCachedConfig(loader, "unresolvedReference", new Callable() { - @Override - public com.gu.typesafe.config.Config call() { - return Parseable.newResources("reference.conf", - ConfigParseOptions.defaults().setClassLoader(loader)) - .parse().toConfig(); - } - }); - } - - /** - * This returns the unresolved reference configuration, but before doing so, - * it verifies that the reference configuration resolves, to ensure that it - * is self contained and doesn't depend on any higher level configuration - * files. - */ - public static Config defaultReferenceUnresolved(final ClassLoader loader) { - // First, verify that `reference.conf` resolves by itself. - try { - defaultReference(loader); - } catch (com.gu.typesafe.config.ConfigException.UnresolvedSubstitution e) { - throw e.addExtraDetail("Could not resolve substitution in reference.conf to a value: %s. All reference.conf files are required to be fully, independently resolvable, and should not require the presence of values for substitutions from further up the hierarchy."); - } - // Now load the unresolved version - return unresolvedReference(loader); - } - - - private static class DebugHolder { - private static String LOADS = "loads"; - private static String SUBSTITUTIONS = "substitutions"; - - private static Map loadDiagnostics() { - Map result = new HashMap(); - result.put(LOADS, false); - result.put(SUBSTITUTIONS, false); - - // People do -Dconfig.trace=foo,bar to enable tracing of different things - String s = System.getProperty("config.trace"); - if (s == null) { - return result; - } else { - String[] keys = s.split(","); - for (String k : keys) { - if (k.equals(LOADS)) { - result.put(LOADS, true); - } else if (k.equals(SUBSTITUTIONS)) { - result.put(SUBSTITUTIONS, true); - } else { - System.err.println("config.trace property contains unknown trace topic '" - + k + "'"); - } - } - return result; - } - } - - private static final Map diagnostics = loadDiagnostics(); - - private static final boolean traceLoadsEnabled = diagnostics.get(LOADS); - private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS); - - static boolean traceLoadsEnabled() { - return traceLoadsEnabled; - } - - static boolean traceSubstitutionsEnabled() { - return traceSubstitutionsEnabled; - } - } - - public static boolean traceLoadsEnabled() { - try { - return DebugHolder.traceLoadsEnabled(); - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - public static boolean traceSubstitutionsEnabled() { - try { - return DebugHolder.traceSubstitutionsEnabled(); - } catch (ExceptionInInitializerError e) { - throw ConfigImplUtil.extractInitializerError(e); - } - } - - public static void trace(String message) { - System.err.println(message); - } - - public static void trace(int indentLevel, String message) { - while (indentLevel > 0) { - System.err.print(" "); - indentLevel -= 1; - } - System.err.println(message); - } - - // the basic idea here is to add the "what" and have a canonical - // toplevel error message. the "original" exception may however have extra - // detail about what happened. call this if you have a better "what" than - // further down on the stack. - static com.gu.typesafe.config.ConfigException.NotResolved improveNotResolved(Path what, - com.gu.typesafe.config.ConfigException.NotResolved original) { - String newMessage = what.render() - + " has not been resolved, you need to call Config#resolve()," - + " see API docs for Config#resolve()"; - if (newMessage.equals(original.getMessage())) - return original; - else - return new ConfigException.NotResolved(newMessage, original); - } - - public static com.gu.typesafe.config.ConfigOrigin newSimpleOrigin(String description) { - if (description == null) { - return defaultValueOrigin; - } else { - return SimpleConfigOrigin.newSimple(description); - } - } - - public static com.gu.typesafe.config.ConfigOrigin newFileOrigin(String filename) { - return SimpleConfigOrigin.newFile(filename); - } - - public static ConfigOrigin newURLOrigin(URL url) { - return SimpleConfigOrigin.newURL(url); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImplUtil.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImplUtil.java deleted file mode 100644 index 17450e68859..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigImplUtil.java +++ /dev/null @@ -1,302 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigSyntax; - -/** - * Internal implementation detail, not ABI stable, do not touch. - * For use only by the {@link com.typesafe.config} package. - */ -final public class ConfigImplUtil { - static boolean equalsHandlingNull(Object a, Object b) { - if (a == null && b != null) - return false; - else if (a != null && b == null) - return false; - else if (a == b) // catches null == null plus optimizes identity case - return true; - else - return a.equals(b); - } - - static boolean isC0Control(int codepoint) { - return (codepoint >= 0x0000 && codepoint <= 0x001F); - } - - public static String renderJsonString(String s) { - StringBuilder sb = new StringBuilder(); - sb.append('"'); - for (int i = 0; i < s.length(); ++i) { - char c = s.charAt(i); - switch (c) { - case '"': - sb.append("\\\""); - break; - case '\\': - sb.append("\\\\"); - break; - case '\n': - sb.append("\\n"); - break; - case '\b': - sb.append("\\b"); - break; - case '\f': - sb.append("\\f"); - break; - case '\r': - sb.append("\\r"); - break; - case '\t': - sb.append("\\t"); - break; - default: - if (isC0Control(c)) - sb.append(String.format("\\u%04x", (int) c)); - else - sb.append(c); - } - } - sb.append('"'); - return sb.toString(); - } - - static String renderStringUnquotedIfPossible(String s) { - // this can quote unnecessarily as long as it never fails to quote when - // necessary - if (s.length() == 0) - return renderJsonString(s); - - // if it starts with a hyphen or number, we have to quote - // to ensure we end up with a string and not a number - int first = s.codePointAt(0); - if (Character.isDigit(first) || first == '-') - return renderJsonString(s); - - if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false") - || s.startsWith("null") || s.contains("//")) - return renderJsonString(s); - - // only unquote if it's pure alphanumeric - for (int i = 0; i < s.length(); ++i) { - char c = s.charAt(i); - if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-')) - return renderJsonString(s); - } - - return s; - } - - static boolean isWhitespace(int codepoint) { - switch (codepoint) { - // try to hit the most common ASCII ones first, then the nonbreaking - // spaces that Java brokenly leaves out of isWhitespace. - case ' ': - case '\n': - case '\u00A0': - case '\u2007': - case '\u202F': - // this one is the BOM, see - // http://www.unicode.org/faq/utf_bom.html#BOM - // we just accept it as a zero-width nonbreaking space. - case '\uFEFF': - return true; - default: - return Character.isWhitespace(codepoint); - } - } - - public static String unicodeTrim(String s) { - // this is dumb because it looks like there aren't any whitespace - // characters that need surrogate encoding. But, points for - // pedantic correctness! It's future-proof or something. - // String.trim() actually is broken, since there are plenty of - // non-ASCII whitespace characters. - final int length = s.length(); - if (length == 0) - return s; - - int start = 0; - while (start < length) { - char c = s.charAt(start); - if (c == ' ' || c == '\n') { - start += 1; - } else { - int cp = s.codePointAt(start); - if (isWhitespace(cp)) - start += Character.charCount(cp); - else - break; - } - } - - int end = length; - while (end > start) { - char c = s.charAt(end - 1); - if (c == ' ' || c == '\n') { - --end; - } else { - int cp; - int delta; - if (Character.isLowSurrogate(c)) { - cp = s.codePointAt(end - 2); - delta = 2; - } else { - cp = s.codePointAt(end - 1); - delta = 1; - } - if (isWhitespace(cp)) - end -= delta; - else - break; - } - } - return s.substring(start, end); - } - - - public static com.gu.typesafe.config.ConfigException extractInitializerError(ExceptionInInitializerError e) { - Throwable cause = e.getCause(); - if (cause != null && cause instanceof com.gu.typesafe.config.ConfigException) { - return (com.gu.typesafe.config.ConfigException) cause; - } else { - throw e; - } - } - - static File urlToFile(URL url) { - // this isn't really right, clearly, but not sure what to do. - try { - // this will properly handle hex escapes, etc. - return new File(url.toURI()); - } catch (URISyntaxException e) { - // this handles some stuff like file:///c:/Whatever/ - // apparently but mangles handling of hex escapes - return new File(url.getPath()); - } catch (IllegalArgumentException e) { - // file://foo with double slash causes - // IllegalArgumentException "url has an authority component" - return new File(url.getPath()); - } - } - - public static String joinPath(String... elements) { - return (new Path(elements)).render(); - } - - public static String joinPath(List elements) { - return joinPath(elements.toArray(new String[0])); - } - - public static List splitPath(String path) { - Path p = Path.newPath(path); - List elements = new ArrayList(); - while (p != null) { - elements.add(p.first()); - p = p.remainder(); - } - return elements; - } - - public static com.gu.typesafe.config.ConfigOrigin readOrigin(ObjectInputStream in) throws IOException { - return com.gu.typesafe.config.impl.SerializedConfigValue.readOrigin(in, null); - } - - public static void writeOrigin(ObjectOutputStream out, ConfigOrigin origin) throws IOException { - com.gu.typesafe.config.impl.SerializedConfigValue.writeOrigin(new DataOutputStream(out), (com.gu.typesafe.config.impl.SimpleConfigOrigin) origin, - null); - } - - static String toCamelCase(String originalName) { - String[] words = originalName.split("-+"); - StringBuilder nameBuilder = new StringBuilder(originalName.length()); - for (String word : words) { - if (nameBuilder.length() == 0) { - nameBuilder.append(word); - } else { - nameBuilder.append(word.substring(0, 1).toUpperCase()); - nameBuilder.append(word.substring(1)); - } - } - return nameBuilder.toString(); - } - - private static char underscoreMappings(int num) { - // Rationale on name mangling: - // - // Most shells (e.g. bash, sh, etc.) doesn't support any character other - // than alphanumeric and `_` in environment variables names. - // In HOCON the default separator is `.` so it is directly translated to a - // single `_` for convenience; `-` and `_` are less often present in config - // keys but they have to be representable and the only possible mapping is - // `_` repeated. - switch (num) { - case 1: return '.'; - case 2: return '-'; - case 3: return '_'; - default: return 0; - } - } - - static String envVariableAsProperty(String variable, String prefix) throws com.gu.typesafe.config.ConfigException { - StringBuilder builder = new StringBuilder(); - - String strippedPrefix = variable.substring(prefix.length(), variable.length()); - - int underscores = 0; - for (char c : strippedPrefix.toCharArray()) { - if (c == '_') { - underscores++; - } else { - if (underscores > 0 && underscores < 4) { - builder.append(underscoreMappings(underscores)); - } else if (underscores > 3) { - throw new com.gu.typesafe.config.ConfigException.BadPath(variable, "Environment variable contains an un-mapped number of underscores."); - } - underscores = 0; - builder.append(c); - } - } - - if (underscores > 0 && underscores < 4) { - builder.append(underscoreMappings(underscores)); - } else if (underscores > 3) { - throw new ConfigException.BadPath(variable, "Environment variable contains an un-mapped number of underscores."); - } - - return builder.toString(); - } - - /** - * Guess configuration syntax from given filename. - * - * @param filename configuration filename - * @return configuration syntax if a match is found. Otherwise, null. - */ - public static com.gu.typesafe.config.ConfigSyntax syntaxFromExtension(String filename) { - if (filename == null) - return null; - else if (filename.endsWith(".json")) - return com.gu.typesafe.config.ConfigSyntax.JSON; - else if (filename.endsWith(".conf")) - return com.gu.typesafe.config.ConfigSyntax.CONF; - else if (filename.endsWith(".properties")) - return ConfigSyntax.PROPERTIES; - else - return null; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigIncludeKind.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigIncludeKind.java deleted file mode 100644 index 4ba2530d1e0..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigIncludeKind.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.gu.typesafe.config.impl; - -enum ConfigIncludeKind { - URL, FILE, CLASSPATH, HEURISTIC -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigInt.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigInt.java deleted file mode 100644 index 5b8ebf8eb0a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigInt.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; - -final class ConfigInt extends ConfigNumber implements Serializable { - - private static final long serialVersionUID = 2L; - - final private int value; - - ConfigInt(com.gu.typesafe.config.ConfigOrigin origin, int value, String originalText) { - super(origin, originalText); - this.value = value; - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.NUMBER; - } - - @Override - public Integer unwrapped() { - return value; - } - - @Override - String transformToString() { - String s = super.transformToString(); - if (s == null) - return Integer.toString(value); - else - return s; - } - - @Override - protected long longValue() { - return value; - } - - @Override - protected double doubleValue() { - return value; - } - - @Override - protected ConfigInt newCopy(ConfigOrigin origin) { - return new ConfigInt(origin, value, originalText); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigLong.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigLong.java deleted file mode 100644 index 6d6f92acc97..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigLong.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; - -final class ConfigLong extends com.gu.typesafe.config.impl.ConfigNumber implements Serializable { - - private static final long serialVersionUID = 2L; - - final private long value; - - ConfigLong(com.gu.typesafe.config.ConfigOrigin origin, long value, String originalText) { - super(origin, originalText); - this.value = value; - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.NUMBER; - } - - @Override - public Long unwrapped() { - return value; - } - - @Override - String transformToString() { - String s = super.transformToString(); - if (s == null) - return Long.toString(value); - else - return s; - } - - @Override - protected long longValue() { - return value; - } - - @Override - protected double doubleValue() { - return value; - } - - @Override - protected ConfigLong newCopy(ConfigOrigin origin) { - return new ConfigLong(origin, value, originalText); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeArray.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeArray.java deleted file mode 100644 index e526ddad868..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeArray.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.Collection; - -final class ConfigNodeArray extends com.gu.typesafe.config.impl.ConfigNodeComplexValue { - ConfigNodeArray(Collection children) { - super(children); - } - - @Override - protected ConfigNodeArray newNode(Collection nodes) { - return new ConfigNodeArray(nodes); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComment.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComment.java deleted file mode 100644 index de4e8ada16d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComment.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.gu.typesafe.config.impl; - - -import com.gu.typesafe.config.ConfigException; - -final class ConfigNodeComment extends ConfigNodeSingleToken { - ConfigNodeComment(Token comment) { - super(comment); - if (!Tokens.isComment(super.token)) { - throw new ConfigException.BugOrBroken("Tried to create a ConfigNodeComment from a non-comment token"); - } - } - - protected String commentText() { - return Tokens.getCommentText(super.token); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComplexValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComplexValue.java deleted file mode 100644 index 3d2a509cd37..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeComplexValue.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.*; - -abstract class ConfigNodeComplexValue extends AbstractConfigNodeValue { - final protected ArrayList children; - - ConfigNodeComplexValue(Collection children) { - this.children = new ArrayList(children); - } - - final public Collection children() { - return children; - } - - @Override - protected Collection tokens() { - ArrayList tokens = new ArrayList(); - for (AbstractConfigNode child : children) { - tokens.addAll(child.tokens()); - } - return tokens; - } - - protected ConfigNodeComplexValue indentText(AbstractConfigNode indentation) { - ArrayList childrenCopy = new ArrayList(children); - for (int i = 0; i < childrenCopy.size(); i++) { - AbstractConfigNode child = childrenCopy.get(i); - if (child instanceof ConfigNodeSingleToken && - Tokens.isNewline(((ConfigNodeSingleToken) child).token())) { - childrenCopy.add(i + 1, indentation); - i++; - } else if (child instanceof ConfigNodeField) { - AbstractConfigNode value = ((ConfigNodeField) child).value(); - if (value instanceof ConfigNodeComplexValue) { - childrenCopy.set(i, ((ConfigNodeField) child).replaceValue(((ConfigNodeComplexValue) value).indentText(indentation))); - } - } else if (child instanceof ConfigNodeComplexValue) { - childrenCopy.set(i, ((ConfigNodeComplexValue) child).indentText(indentation)); - } - } - return newNode(childrenCopy); - } - - // This method will just call into the object's constructor, but it's needed - // for use in the indentText() method so we can avoid a gross if/else statement - // checking the type of this - abstract ConfigNodeComplexValue newNode(Collection nodes); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeConcatenation.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeConcatenation.java deleted file mode 100644 index 3d81a6f0ab2..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeConcatenation.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.Collection; - -final class ConfigNodeConcatenation extends com.gu.typesafe.config.impl.ConfigNodeComplexValue { - ConfigNodeConcatenation(Collection children) { - super(children); - } - - @Override - protected ConfigNodeConcatenation newNode(Collection nodes) { - return new ConfigNodeConcatenation(nodes); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeField.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeField.java deleted file mode 100644 index 86c2260ea37..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeField.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -final class ConfigNodeField extends com.gu.typesafe.config.impl.AbstractConfigNode { - final private ArrayList children; - - public ConfigNodeField(Collection children) { - this.children = new ArrayList(children); - } - - @Override - protected Collection tokens() { - ArrayList tokens = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigNode child : children) { - tokens.addAll(child.tokens()); - } - return tokens; - } - - public ConfigNodeField replaceValue(com.gu.typesafe.config.impl.AbstractConfigNodeValue newValue) { - ArrayList childrenCopy = new ArrayList(children); - for (int i = 0; i < childrenCopy.size(); i++) { - if (childrenCopy.get(i) instanceof com.gu.typesafe.config.impl.AbstractConfigNodeValue) { - childrenCopy.set(i, newValue); - return new ConfigNodeField(childrenCopy); - } - } - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Field node doesn't have a value"); - } - - public com.gu.typesafe.config.impl.AbstractConfigNodeValue value() { - for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof com.gu.typesafe.config.impl.AbstractConfigNodeValue) { - return (com.gu.typesafe.config.impl.AbstractConfigNodeValue)children.get(i); - } - } - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Field node doesn't have a value"); - } - - public com.gu.typesafe.config.impl.ConfigNodePath path() { - for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodePath) { - return (com.gu.typesafe.config.impl.ConfigNodePath)children.get(i); - } - } - throw new ConfigException.BugOrBroken("Field node doesn't have a path"); - } - - protected com.gu.typesafe.config.impl.Token separator() { - for (com.gu.typesafe.config.impl.AbstractConfigNode child : children) { - if (child instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - com.gu.typesafe.config.impl.Token t = ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) child).token(); - if (t == com.gu.typesafe.config.impl.Tokens.PLUS_EQUALS || t == com.gu.typesafe.config.impl.Tokens.COLON || t == com.gu.typesafe.config.impl.Tokens.EQUALS) { - return t; - } - } - } - return null; - } - - protected List comments() { - List comments = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigNode child : children) { - if (child instanceof com.gu.typesafe.config.impl.ConfigNodeComment) { - comments.add(((com.gu.typesafe.config.impl.ConfigNodeComment) child).commentText()); - } - } - return comments; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeInclude.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeInclude.java deleted file mode 100644 index 8b2ed56ee17..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeInclude.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collection; - -final class ConfigNodeInclude extends com.gu.typesafe.config.impl.AbstractConfigNode { - final private ArrayList children; - final private ConfigIncludeKind kind; - final private boolean isRequired; - - ConfigNodeInclude(Collection children, ConfigIncludeKind kind, boolean isRequired) { - this.children = new ArrayList(children); - this.kind = kind; - this.isRequired = isRequired; - } - - final public Collection children() { - return children; - } - - @Override - protected Collection tokens() { - ArrayList tokens = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigNode child : children) { - tokens.addAll(child.tokens()); - } - return tokens; - } - - protected ConfigIncludeKind kind() { - return kind; - } - - protected boolean isRequired() { - return isRequired; - } - - protected String name() { - for (com.gu.typesafe.config.impl.AbstractConfigNode n : children) { - if (n instanceof com.gu.typesafe.config.impl.ConfigNodeSimpleValue) { - return (String) com.gu.typesafe.config.impl.Tokens.getValue(((com.gu.typesafe.config.impl.ConfigNodeSimpleValue) n).token()).unwrapped(); - } - } - return null; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeObject.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeObject.java deleted file mode 100644 index 30d260416f4..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeObject.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigSyntax; - -import java.util.ArrayList; -import java.util.Collection; - -final class ConfigNodeObject extends com.gu.typesafe.config.impl.ConfigNodeComplexValue { - ConfigNodeObject(Collection children) { - super(children); - } - - @Override - protected ConfigNodeObject newNode(Collection nodes) { - return new ConfigNodeObject(nodes); - } - - public boolean hasValue(Path desiredPath) { - for (AbstractConfigNode node : children) { - if (node instanceof ConfigNodeField) { - ConfigNodeField field = (ConfigNodeField) node; - Path key = field.path().value(); - if (key.equals(desiredPath) || key.startsWith(desiredPath)) { - return true; - } else if (desiredPath.startsWith(key)) { - if (field.value() instanceof ConfigNodeObject) { - ConfigNodeObject obj = (ConfigNodeObject) field.value(); - Path remainingPath = desiredPath.subPath(key.length()); - if (obj.hasValue(remainingPath)) { - return true; - } - } - } - } - } - return false; - } - - protected ConfigNodeObject changeValueOnPath(Path desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value, com.gu.typesafe.config.ConfigSyntax flavor) { - ArrayList childrenCopy = new ArrayList(super.children); - boolean seenNonMatching = false; - // Copy the value so we can change it to null but not modify the original parameter - com.gu.typesafe.config.impl.AbstractConfigNodeValue valueCopy = value; - for (int i = childrenCopy.size() - 1; i >= 0; i--) { - if (childrenCopy.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - Token t = ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) childrenCopy.get(i)).token(); - // Ensure that, when we are removing settings in JSON, we don't end up with a trailing comma - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON && !seenNonMatching && t == com.gu.typesafe.config.impl.Tokens.COMMA) { - childrenCopy.remove(i); - } - continue; - } else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { - continue; - } - ConfigNodeField node = (ConfigNodeField) childrenCopy.get(i); - Path key = node.path().value(); - - // Delete all multi-element paths that start with the desired path, since technically they are duplicates - if ((valueCopy == null && key.equals(desiredPath))|| (key.startsWith(desiredPath) && !key.equals(desiredPath))) { - childrenCopy.remove(i); - // Remove any whitespace or commas after the deleted setting - for (int j = i; j < childrenCopy.size(); j++) { - if (childrenCopy.get(j) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - Token t = ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) childrenCopy.get(j)).token(); - if (com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(t) || t == com.gu.typesafe.config.impl.Tokens.COMMA) { - childrenCopy.remove(j); - j--; - } else { - break; - } - } else { - break; - } - } - } else if (key.equals(desiredPath)) { - seenNonMatching = true; - com.gu.typesafe.config.impl.AbstractConfigNodeValue indentedValue; - AbstractConfigNode before = i - 1 > 0 ? childrenCopy.get(i - 1) : null; - if (value instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue && - before instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) before).token())) - indentedValue = ((com.gu.typesafe.config.impl.ConfigNodeComplexValue) value).indentText(before); - else - indentedValue = value; - childrenCopy.set(i, node.replaceValue(indentedValue)); - valueCopy = null; - } else if (desiredPath.startsWith(key)) { - seenNonMatching = true; - if (node.value() instanceof ConfigNodeObject) { - Path remainingPath = desiredPath.subPath(key.length()); - childrenCopy.set(i, node.replaceValue(((ConfigNodeObject) node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); - if (valueCopy != null && !node.equals(super.children.get(i))) - valueCopy = null; - } - } else { - seenNonMatching = true; - } - } - return new ConfigNodeObject(childrenCopy); - } - - public ConfigNodeObject setValueOnPath(String desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value) { - return setValueOnPath(desiredPath, value, com.gu.typesafe.config.ConfigSyntax.CONF); - } - - public ConfigNodeObject setValueOnPath(String desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value, com.gu.typesafe.config.ConfigSyntax flavor) { - com.gu.typesafe.config.impl.ConfigNodePath path = com.gu.typesafe.config.impl.PathParser.parsePathNode(desiredPath, flavor); - return setValueOnPath(path, value, flavor); - } - - private ConfigNodeObject setValueOnPath(com.gu.typesafe.config.impl.ConfigNodePath desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value, com.gu.typesafe.config.ConfigSyntax flavor) { - ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor); - - // If the desired Path did not exist, add it - if (!node.hasValue(desiredPath.value())) { - return node.addValueOnPath(desiredPath, value, flavor); - } - return node; - } - - private Collection indentation() { - boolean seenNewLine = false; - ArrayList indentation = new ArrayList(); - if (children.isEmpty()) { - return indentation; - } - for (int i = 0; i < children.size(); i++) { - if (!seenNewLine) { - if (children.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) children.get(i)).token())) { - seenNewLine = true; - indentation.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newLine(null))); - } - } else { - if (children.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) children.get(i)).token()) && - i + 1 < children.size() && (children.get(i+1) instanceof ConfigNodeField || - children.get(i+1) instanceof ConfigNodeInclude)) { - // Return the indentation of the first setting on its own line - indentation.add(children.get(i)); - return indentation; - } - } - } - if (indentation.isEmpty()) { - indentation.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newIgnoredWhitespace(null, " "))); - } else { - // Calculate the indentation of the ending curly-brace to get the indentation of the root object - AbstractConfigNode last = children.get(children.size() - 1); - if (last instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) last).token() == com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY) { - AbstractConfigNode beforeLast = children.get(children.size() - 2); - String indent = ""; - if (beforeLast instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) beforeLast).token())) - indent = ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) beforeLast).token().tokenText(); - indent += " "; - indentation.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newIgnoredWhitespace(null, indent))); - return indentation; - } - } - - // The object has no curly braces and is at the root level, so don't indent - return indentation; - } - - protected ConfigNodeObject addValueOnPath(com.gu.typesafe.config.impl.ConfigNodePath desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value, com.gu.typesafe.config.ConfigSyntax flavor) { - Path path = desiredPath.value(); - ArrayList childrenCopy = new ArrayList(super.children); - ArrayList indentation = new ArrayList(indentation()); - - // If the value we're inserting is a complex value, we'll need to indent it for insertion - com.gu.typesafe.config.impl.AbstractConfigNodeValue indentedValue; - if (value instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue && !indentation.isEmpty()) { - indentedValue = ((com.gu.typesafe.config.impl.ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1)); - } else { - indentedValue = value; - } - boolean sameLine = !(indentation.size() > 0 && indentation.get(0) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) indentation.get(0)).token())); - - // If the path is of length greater than one, see if the value needs to be added further down - if (path.length() > 1) { - for (int i = super.children.size() - 1; i >= 0; i--) { - if (!(super.children.get(i) instanceof ConfigNodeField)) { - continue; - } - ConfigNodeField node = (ConfigNodeField) super.children.get(i); - Path key = node.path().value(); - if (path.startsWith(key) && node.value() instanceof ConfigNodeObject) { - com.gu.typesafe.config.impl.ConfigNodePath remainingPath = desiredPath.subPath(key.length()); - ConfigNodeObject newValue = (ConfigNodeObject) node.value(); - childrenCopy.set(i, node.replaceValue(newValue.addValueOnPath(remainingPath, value, flavor))); - return new ConfigNodeObject(childrenCopy); - } - } - } - - // Otherwise, construct the new setting - boolean startsWithBrace = !super.children.isEmpty() && super.children.get(0) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) super.children.get(0)).token() == com.gu.typesafe.config.impl.Tokens.OPEN_CURLY; - ArrayList newNodes = new ArrayList(); - newNodes.addAll(indentation); - newNodes.add(desiredPath.first()); - newNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newIgnoredWhitespace(null, " "))); - newNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.COLON)); - newNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newIgnoredWhitespace(null, " "))); - - if (path.length() == 1) { - newNodes.add(indentedValue); - } else { - // If the path is of length greater than one add the required new objects along the path - ArrayList newObjectNodes = new ArrayList(); - newObjectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.OPEN_CURLY)); - if (indentation.isEmpty()) { - newObjectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.newLine(null))); - } - newObjectNodes.addAll(indentation); - newObjectNodes.add(new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY)); - ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes); - newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor)); - } - - // Combine these two cases so that we only have to iterate once - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON || startsWithBrace || sameLine) { - for (int i = childrenCopy.size() - 1; i >= 0; i--) { - - // If we are in JSON or are adding a setting on the same line, we need to add a comma to the - // last setting - if ((flavor == com.gu.typesafe.config.ConfigSyntax.JSON || sameLine) && childrenCopy.get(i) instanceof ConfigNodeField) { - if (i+1 >= childrenCopy.size() || - !(childrenCopy.get(i+1) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken - && ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) childrenCopy.get(i+1)).token() == com.gu.typesafe.config.impl.Tokens.COMMA)) - childrenCopy.add(i+1, new com.gu.typesafe.config.impl.ConfigNodeSingleToken(com.gu.typesafe.config.impl.Tokens.COMMA)); - break; - } - - // Add the value into the copy of the children map, keeping any whitespace/newlines - // before the close curly brace - if (startsWithBrace && childrenCopy.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) childrenCopy.get(i)).token == com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY) { - AbstractConfigNode previous = childrenCopy.get(i - 1); - if (previous instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) previous).token())) { - childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); - i--; - } else if (previous instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) previous).token())) { - AbstractConfigNode beforePrevious = childrenCopy.get(i - 2); - if (sameLine) { - childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); - i--; - } - else if (beforePrevious instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) beforePrevious).token())) { - childrenCopy.add(i - 2, new ConfigNodeField(newNodes)); - i -= 2; - } else { - childrenCopy.add(i, new ConfigNodeField(newNodes)); - } - - } - else - childrenCopy.add(i, new ConfigNodeField(newNodes)); - } - } - } - if (!startsWithBrace) { - if (!childrenCopy.isEmpty() && childrenCopy.get(childrenCopy.size() - 1) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && - com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) childrenCopy.get(childrenCopy.size() - 1)).token())) - childrenCopy.add(childrenCopy.size() - 1, new ConfigNodeField(newNodes)); - else - childrenCopy.add(new ConfigNodeField(newNodes)); - } - return new ConfigNodeObject(childrenCopy); - } - - public ConfigNodeObject removeValueOnPath(String desiredPath, ConfigSyntax flavor) { - Path path = com.gu.typesafe.config.impl.PathParser.parsePathNode(desiredPath, flavor).value(); - return changeValueOnPath(path, null, flavor); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodePath.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodePath.java deleted file mode 100644 index c0bc4a53499..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodePath.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; - -import java.util.ArrayList; -import java.util.Collection; - -final class ConfigNodePath extends AbstractConfigNode { - final private Path path; - final ArrayList tokens; - ConfigNodePath(Path path, Collection tokens) { - this.path = path; - this.tokens = new ArrayList(tokens); - } - - @Override - protected Collection tokens() { - return tokens; - } - - protected Path value() { - return path; - } - - protected ConfigNodePath subPath(int toRemove) { - int periodCount = 0; - ArrayList tokensCopy = new ArrayList(tokens); - for (int i = 0; i < tokensCopy.size(); i++) { - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(tokensCopy.get(i)) && - tokensCopy.get(i).tokenText().equals(".")) - periodCount++; - - if (periodCount == toRemove) { - return new ConfigNodePath(path.subPath(toRemove), tokensCopy.subList(i + 1, tokensCopy.size())); - } - } - throw new ConfigException.BugOrBroken("Tried to remove too many elements from a Path node"); - } - - protected ConfigNodePath first() { - ArrayList tokensCopy = new ArrayList(tokens); - for (int i = 0; i < tokensCopy.size(); i++) { - if (com.gu.typesafe.config.impl.Tokens.isUnquotedText(tokensCopy.get(i)) && - tokensCopy.get(i).tokenText().equals(".")) - return new ConfigNodePath(path.subPath(0, 1), tokensCopy.subList(0, i)); - } - return this; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeRoot.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeRoot.java deleted file mode 100644 index fa8c4c45298..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeRoot.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigSyntax; - -import java.util.ArrayList; -import java.util.Collection; - -final class ConfigNodeRoot extends com.gu.typesafe.config.impl.ConfigNodeComplexValue { - final private com.gu.typesafe.config.ConfigOrigin origin; - - ConfigNodeRoot(Collection children, ConfigOrigin origin) { - super(children); - this.origin = origin; - } - - @Override - protected ConfigNodeRoot newNode(Collection nodes) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Tried to indent the root object"); - } - - protected com.gu.typesafe.config.impl.ConfigNodeComplexValue value() { - for (com.gu.typesafe.config.impl.AbstractConfigNode node : children) { - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue) { - return (com.gu.typesafe.config.impl.ConfigNodeComplexValue)node; - } - } - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); - } - - protected ConfigNodeRoot setValue(String desiredPath, com.gu.typesafe.config.impl.AbstractConfigNodeValue value, ConfigSyntax flavor) { - ArrayList childrenCopy = new ArrayList(children); - for (int i = 0; i < childrenCopy.size(); i++) { - com.gu.typesafe.config.impl.AbstractConfigNode node = childrenCopy.get(i); - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue) { - if (node instanceof ConfigNodeArray) { - throw new com.gu.typesafe.config.ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeObject) { - if (value == null) { - childrenCopy.set(i, ((com.gu.typesafe.config.impl.ConfigNodeObject)node).removeValueOnPath(desiredPath, flavor)); - } else { - childrenCopy.set(i, ((com.gu.typesafe.config.impl.ConfigNodeObject) node).setValueOnPath(desiredPath, value, flavor)); - } - return new ConfigNodeRoot(childrenCopy, origin); - } - } - } - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); - } - - protected boolean hasValue(String desiredPath) { - Path path = com.gu.typesafe.config.impl.PathParser.parsePath(desiredPath); - ArrayList childrenCopy = new ArrayList(children); - for (int i = 0; i < childrenCopy.size(); i++) { - com.gu.typesafe.config.impl.AbstractConfigNode node = childrenCopy.get(i); - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue) { - if (node instanceof ConfigNodeArray) { - throw new com.gu.typesafe.config.ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeObject) { - return ((com.gu.typesafe.config.impl.ConfigNodeObject) node).hasValue(path); - } - } - } - throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSimpleValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSimpleValue.java deleted file mode 100644 index 63d3f7b08c0..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSimpleValue.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -final class ConfigNodeSimpleValue extends AbstractConfigNodeValue { - final Token token; - ConfigNodeSimpleValue(Token value) { - token = value; - } - - @Override - protected Collection tokens() { - return Collections.singletonList(token); - } - - protected Token token() { return token; } - - protected com.gu.typesafe.config.impl.AbstractConfigValue value() { - if (Tokens.isValue(token)) - return Tokens.getValue(token); - else if (Tokens.isUnquotedText(token)) - return new ConfigString.Unquoted(token.origin(), Tokens.getUnquotedText(token)); - else if (Tokens.isSubstitution(token)) { - List expression = Tokens.getSubstitutionPathExpression(token); - Path path = PathParser.parsePathExpression(expression.iterator(), token.origin()); - boolean optional = Tokens.getSubstitutionOptional(token); - - return new ConfigReference(token.origin(), new SubstitutionExpression(path, optional)); - } - throw new ConfigException.BugOrBroken("ConfigNodeSimpleValue did not contain a valid value token"); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSingleToken.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSingleToken.java deleted file mode 100644 index 442006a1d7f..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNodeSingleToken.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.Collection; -import java.util.Collections; - -class ConfigNodeSingleToken extends AbstractConfigNode { - final Token token; - ConfigNodeSingleToken(Token t) { - token = t; - } - - @Override - protected Collection tokens() { - return Collections.singletonList(token); - } - - protected Token token() { return token; } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNull.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNull.java deleted file mode 100644 index 607c4de844c..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNull.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigRenderOptions; -import com.gu.typesafe.config.ConfigValueType; - -/** - * This exists because sometimes null is not the same as missing. Specifically, - * if a value is set to null we can give a better error message (indicating - * where it was set to null) in case someone asks for the value. Also, null - * overrides values set "earlier" in the search path, while missing values do - * not. - * - */ -final class ConfigNull extends com.gu.typesafe.config.impl.AbstractConfigValue implements Serializable { - - private static final long serialVersionUID = 2L; - - ConfigNull(com.gu.typesafe.config.ConfigOrigin origin) { - super(origin); - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.NULL; - } - - @Override - public Object unwrapped() { - return null; - } - - @Override - String transformToString() { - return "null"; - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - sb.append("null"); - } - - @Override - protected ConfigNull newCopy(ConfigOrigin origin) { - return new ConfigNull(origin); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNumber.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNumber.java deleted file mode 100644 index c16d33e10b7..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigNumber.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; - -abstract class ConfigNumber extends com.gu.typesafe.config.impl.AbstractConfigValue implements Serializable { - - private static final long serialVersionUID = 2L; - - // This is so when we concatenate a number into a string (say it appears in - // a sentence) we always have it exactly as the person typed it into the - // config file. It's purely cosmetic; equals/hashCode don't consider this - // for example. - final protected String originalText; - - protected ConfigNumber(com.gu.typesafe.config.ConfigOrigin origin, String originalText) { - super(origin); - this.originalText = originalText; - } - - @Override - public abstract Number unwrapped(); - - @Override - String transformToString() { - return originalText; - } - - int intValueRangeChecked(String path) { - long l = longValue(); - if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { - throw new ConfigException.WrongType(origin(), path, "32-bit integer", - "out-of-range value " + l); - } - return (int) l; - } - - protected abstract long longValue(); - - protected abstract double doubleValue(); - - private boolean isWhole() { - long asLong = longValue(); - return asLong == doubleValue(); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof ConfigNumber; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigNumber && canEqual(other)) { - ConfigNumber n = (ConfigNumber) other; - if (isWhole()) { - return n.isWhole() && this.longValue() == n.longValue(); - } else { - return (!n.isWhole()) && this.doubleValue() == n.doubleValue(); - } - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - - // this matches what standard Long.hashCode and Double.hashCode - // do, though I don't think it really matters. - long asLong; - if (isWhole()) { - asLong = longValue(); - } else { - asLong = Double.doubleToLongBits(doubleValue()); - } - return (int) (asLong ^ (asLong >>> 32)); - } - - static ConfigNumber newNumber(com.gu.typesafe.config.ConfigOrigin origin, long number, - String originalText) { - if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) - return new com.gu.typesafe.config.impl.ConfigInt(origin, (int) number, originalText); - else - return new ConfigLong(origin, number, originalText); - } - - static ConfigNumber newNumber(ConfigOrigin origin, double number, - String originalText) { - long asLong = (long) number; - if (asLong == number) { - return newNumber(origin, asLong, originalText); - } else { - return new com.gu.typesafe.config.impl.ConfigDouble(origin, number, originalText); - } - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigParser.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigParser.java deleted file mode 100644 index 3a12fdec7be..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigParser.java +++ /dev/null @@ -1,422 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; - -import com.gu.typesafe.config.ConfigException; - -final class ConfigParser { - static com.gu.typesafe.config.impl.AbstractConfigValue parse(com.gu.typesafe.config.impl.ConfigNodeRoot document, - com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.ConfigParseOptions options, - com.gu.typesafe.config.ConfigIncludeContext includeContext) { - ParseContext context = new ParseContext(options.getSyntax(), origin, document, - com.gu.typesafe.config.impl.SimpleIncluder.makeFull(options.getIncluder()), includeContext); - return context.parse(); - } - - static private final class ParseContext { - private int lineNumber; - final private com.gu.typesafe.config.impl.ConfigNodeRoot document; - final private com.gu.typesafe.config.impl.FullIncluder includer; - final private com.gu.typesafe.config.ConfigIncludeContext includeContext; - final private com.gu.typesafe.config.ConfigSyntax flavor; - final private com.gu.typesafe.config.ConfigOrigin baseOrigin; - final private LinkedList pathStack; - - // the number of lists we are inside; this is used to detect the "cannot - // generate a reference to a list element" problem, and once we fix that - // problem we should be able to get rid of this variable. - int arrayCount; - - ParseContext(com.gu.typesafe.config.ConfigSyntax flavor, com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.impl.ConfigNodeRoot document, - com.gu.typesafe.config.impl.FullIncluder includer, com.gu.typesafe.config.ConfigIncludeContext includeContext) { - lineNumber = 1; - this.document = document; - this.flavor = flavor; - this.baseOrigin = origin; - this.includer = includer; - this.includeContext = includeContext; - this.pathStack = new LinkedList(); - this.arrayCount = 0; - } - - // merge a bunch of adjacent values into one - // value; change unquoted text into a string - // value. - private com.gu.typesafe.config.impl.AbstractConfigValue parseConcatenation(com.gu.typesafe.config.impl.ConfigNodeConcatenation n) { - // this trick is not done in JSON - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Found a concatenation node in JSON"); - - List values = new ArrayList(n.children().size()); - - for (com.gu.typesafe.config.impl.AbstractConfigNode node : n.children()) { - com.gu.typesafe.config.impl.AbstractConfigValue v = null; - if (node instanceof com.gu.typesafe.config.impl.AbstractConfigNodeValue) { - v = parseValue((com.gu.typesafe.config.impl.AbstractConfigNodeValue)node, null); - values.add(v); - } - } - - return com.gu.typesafe.config.impl.ConfigConcatenation.concatenate(values); - } - - private com.gu.typesafe.config.impl.SimpleConfigOrigin lineOrigin() { - return ((com.gu.typesafe.config.impl.SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); - } - - private com.gu.typesafe.config.ConfigException parseError(String message) { - return parseError(message, null); - } - - private com.gu.typesafe.config.ConfigException parseError(String message, Throwable cause) { - return new com.gu.typesafe.config.ConfigException.Parse(lineOrigin(), message, cause); - } - - private Path fullCurrentPath() { - // pathStack has top of stack at front - if (pathStack.isEmpty()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Bug in parser; tried to get current path when at root"); - else - return new Path(pathStack.descendingIterator()); - } - - private com.gu.typesafe.config.impl.AbstractConfigValue parseValue(com.gu.typesafe.config.impl.AbstractConfigNodeValue n, List comments) { - com.gu.typesafe.config.impl.AbstractConfigValue v; - - int startingArrayCount = arrayCount; - - if (n instanceof com.gu.typesafe.config.impl.ConfigNodeSimpleValue) { - v = ((com.gu.typesafe.config.impl.ConfigNodeSimpleValue) n).value(); - } else if (n instanceof com.gu.typesafe.config.impl.ConfigNodeObject) { - v = parseObject((com.gu.typesafe.config.impl.ConfigNodeObject)n); - } else if (n instanceof com.gu.typesafe.config.impl.ConfigNodeArray) { - v = parseArray((com.gu.typesafe.config.impl.ConfigNodeArray)n); - } else if (n instanceof com.gu.typesafe.config.impl.ConfigNodeConcatenation) { - v = parseConcatenation((com.gu.typesafe.config.impl.ConfigNodeConcatenation)n); - } else { - throw parseError("Expecting a value but got wrong node type: " + n.getClass()); - } - - if (comments != null && !comments.isEmpty()) { - v = v.withOrigin(v.origin().prependComments(new ArrayList(comments))); - comments.clear(); - } - - if (arrayCount != startingArrayCount) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Bug in config parser: unbalanced array count"); - - return v; - } - - private static com.gu.typesafe.config.impl.AbstractConfigObject createValueUnderPath(Path path, - com.gu.typesafe.config.impl.AbstractConfigValue value) { - // for path foo.bar, we are creating - // { "foo" : { "bar" : value } } - List keys = new ArrayList(); - - String key = path.first(); - Path remaining = path.remainder(); - while (key != null) { - keys.add(key); - if (remaining == null) { - break; - } else { - key = remaining.first(); - remaining = remaining.remainder(); - } - } - - // the withComments(null) is to ensure comments are only - // on the exact leaf node they apply to. - // a comment before "foo.bar" applies to the full setting - // "foo.bar" not also to "foo" - ListIterator i = keys.listIterator(keys.size()); - String deepest = i.previous(); - com.gu.typesafe.config.impl.AbstractConfigObject o = new com.gu.typesafe.config.impl.SimpleConfigObject(value.origin().withComments(null), - Collections. singletonMap( - deepest, value)); - while (i.hasPrevious()) { - Map m = Collections. singletonMap( - i.previous(), o); - o = new com.gu.typesafe.config.impl.SimpleConfigObject(value.origin().withComments(null), m); - } - - return o; - } - - private void parseInclude(Map values, com.gu.typesafe.config.impl.ConfigNodeInclude n) { - boolean isRequired = n.isRequired(); - com.gu.typesafe.config.ConfigIncludeContext cic = includeContext.setParseOptions(includeContext.parseOptions().setAllowMissing(!isRequired)); - - com.gu.typesafe.config.impl.AbstractConfigObject obj; - switch (n.kind()) { - case URL: - URL url; - try { - url = new URL(n.name()); - } catch (MalformedURLException e) { - throw parseError("include url() specifies an invalid URL: " + n.name(), e); - } - obj = (com.gu.typesafe.config.impl.AbstractConfigObject) includer.includeURL(cic, url); - break; - - case FILE: - obj = (com.gu.typesafe.config.impl.AbstractConfigObject) includer.includeFile(cic, - new File(n.name())); - break; - - case CLASSPATH: - obj = (com.gu.typesafe.config.impl.AbstractConfigObject) includer.includeResources(cic, n.name()); - break; - - case HEURISTIC: - obj = (com.gu.typesafe.config.impl.AbstractConfigObject) includer - .include(cic, n.name()); - break; - - default: - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("should not be reached"); - } - - // we really should make this work, but for now throwing an - // exception is better than producing an incorrect result. - // See https://github.com/lightbend/config/issues/160 - if (arrayCount > 0 && obj.resolveStatus() != ResolveStatus.RESOLVED) - throw parseError("Due to current limitations of the config parser, when an include statement is nested inside a list value, " - + "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " - + "remove the ${} statements from the included file."); - - if (!pathStack.isEmpty()) { - Path prefix = fullCurrentPath(); - obj = obj.relativized(prefix); - } - - for (String key : obj.keySet()) { - com.gu.typesafe.config.impl.AbstractConfigValue v = obj.get(key); - com.gu.typesafe.config.impl.AbstractConfigValue existing = values.get(key); - if (existing != null) { - values.put(key, v.withFallback(existing)); - } else { - values.put(key, v); - } - } - } - - private com.gu.typesafe.config.impl.AbstractConfigObject parseObject(com.gu.typesafe.config.impl.ConfigNodeObject n) { - Map values = new HashMap(); - com.gu.typesafe.config.impl.SimpleConfigOrigin objectOrigin = lineOrigin(); - boolean lastWasNewline = false; - - ArrayList nodes = new ArrayList(n.children()); - List comments = new ArrayList(); - for (int i = 0; i < nodes.size(); i++) { - com.gu.typesafe.config.impl.AbstractConfigNode node = nodes.get(i); - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComment) { - lastWasNewline = false; - comments.add(((com.gu.typesafe.config.impl.ConfigNodeComment) node).commentText()); - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) node).token())) { - lineNumber++; - if (lastWasNewline) { - // Drop all comments if there was a blank line and start a new comment block - comments.clear(); - } - lastWasNewline = true; - } else if (flavor != com.gu.typesafe.config.ConfigSyntax.JSON && node instanceof com.gu.typesafe.config.impl.ConfigNodeInclude) { - parseInclude(values, (com.gu.typesafe.config.impl.ConfigNodeInclude)node); - lastWasNewline = false; - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeField) { - lastWasNewline = false; - Path path = ((com.gu.typesafe.config.impl.ConfigNodeField) node).path().value(); - comments.addAll(((com.gu.typesafe.config.impl.ConfigNodeField) node).comments()); - - // path must be on-stack while we parse the value - pathStack.push(path); - if (((com.gu.typesafe.config.impl.ConfigNodeField) node).separator() == com.gu.typesafe.config.impl.Tokens.PLUS_EQUALS) { - // we really should make this work, but for now throwing - // an exception is better than producing an incorrect - // result. See - // https://github.com/lightbend/config/issues/160 - if (arrayCount > 0) - throw parseError("Due to current limitations of the config parser, += does not work nested inside a list. " - + "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " - + "You might be able to move the += outside of the list and then refer to it from inside the list with ${}."); - - // because we will put it in an array after the fact so - // we want this to be incremented during the parseValue - // below in order to throw the above exception. - arrayCount += 1; - } - - com.gu.typesafe.config.impl.AbstractConfigNodeValue valueNode; - com.gu.typesafe.config.impl.AbstractConfigValue newValue; - - valueNode = ((com.gu.typesafe.config.impl.ConfigNodeField) node).value(); - - // comments from the key token go to the value token - newValue = parseValue(valueNode, comments); - - if (((com.gu.typesafe.config.impl.ConfigNodeField) node).separator() == com.gu.typesafe.config.impl.Tokens.PLUS_EQUALS) { - arrayCount -= 1; - - List concat = new ArrayList(2); - com.gu.typesafe.config.impl.AbstractConfigValue previousRef = new com.gu.typesafe.config.impl.ConfigReference(newValue.origin(), - new com.gu.typesafe.config.impl.SubstitutionExpression(fullCurrentPath(), true /* optional */)); - com.gu.typesafe.config.impl.AbstractConfigValue list = new com.gu.typesafe.config.impl.SimpleConfigList(newValue.origin(), - Collections.singletonList(newValue)); - concat.add(previousRef); - concat.add(list); - newValue = com.gu.typesafe.config.impl.ConfigConcatenation.concatenate(concat); - } - - // Grab any trailing comments on the same line - if (i < nodes.size() - 1) { - i++; - while (i < nodes.size()) { - if (nodes.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeComment) { - com.gu.typesafe.config.impl.ConfigNodeComment comment = (com.gu.typesafe.config.impl.ConfigNodeComment) nodes.get(i); - newValue = newValue.withOrigin(newValue.origin().appendComments( - Collections.singletonList(comment.commentText()))); - break; - } else if (nodes.get(i) instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - com.gu.typesafe.config.impl.ConfigNodeSingleToken curr = (com.gu.typesafe.config.impl.ConfigNodeSingleToken) nodes.get(i); - if (curr.token() == com.gu.typesafe.config.impl.Tokens.COMMA || com.gu.typesafe.config.impl.Tokens.isIgnoredWhitespace(curr.token())) { - // keep searching, as there could still be a comment - } else { - i--; - break; - } - } else { - i--; - break; - } - i++; - } - } - - pathStack.pop(); - - String key = path.first(); - Path remaining = path.remainder(); - - if (remaining == null) { - com.gu.typesafe.config.impl.AbstractConfigValue existing = values.get(key); - if (existing != null) { - // In strict JSON, dups should be an error; while in - // our custom config language, they should be merged - // if the value is an object (or substitution that - // could become an object). - - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - throw parseError("JSON does not allow duplicate fields: '" - + key - + "' was already seen at " - + existing.origin().description()); - } else { - newValue = newValue.withFallback(existing); - } - } - values.put(key, newValue); - } else { - if (flavor == com.gu.typesafe.config.ConfigSyntax.JSON) { - throw new ConfigException.BugOrBroken( - "somehow got multi-element path in JSON mode"); - } - - com.gu.typesafe.config.impl.AbstractConfigObject obj = createValueUnderPath( - remaining, newValue); - com.gu.typesafe.config.impl.AbstractConfigValue existing = values.get(key); - if (existing != null) { - obj = obj.withFallback(existing); - } - values.put(key, obj); - } - } - } - - return new com.gu.typesafe.config.impl.SimpleConfigObject(objectOrigin, values); - } - - private com.gu.typesafe.config.impl.SimpleConfigList parseArray(com.gu.typesafe.config.impl.ConfigNodeArray n) { - arrayCount += 1; - - com.gu.typesafe.config.impl.SimpleConfigOrigin arrayOrigin = lineOrigin(); - List values = new ArrayList(); - - boolean lastWasNewLine = false; - List comments = new ArrayList(); - - com.gu.typesafe.config.impl.AbstractConfigValue v = null; - - for (com.gu.typesafe.config.impl.AbstractConfigNode node : n.children()) { - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComment) { - comments.add(((com.gu.typesafe.config.impl.ConfigNodeComment) node).commentText()); - lastWasNewLine = false; - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken && com.gu.typesafe.config.impl.Tokens.isNewline(((com.gu.typesafe.config.impl.ConfigNodeSingleToken) node).token())) { - lineNumber++; - if (lastWasNewLine && v == null) { - comments.clear(); - } else if (v != null) { - values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); - comments.clear(); - v = null; - } - lastWasNewLine = true; - } else if (node instanceof com.gu.typesafe.config.impl.AbstractConfigNodeValue) { - lastWasNewLine = false; - if (v != null) { - values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); - comments.clear(); - } - v = parseValue((com.gu.typesafe.config.impl.AbstractConfigNodeValue)node, comments); - } - } - // There shouldn't be any comments at this point, but add them just in case - if (v != null) { - values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); - } - arrayCount -= 1; - return new com.gu.typesafe.config.impl.SimpleConfigList(arrayOrigin, values); - } - - com.gu.typesafe.config.impl.AbstractConfigValue parse() { - com.gu.typesafe.config.impl.AbstractConfigValue result = null; - ArrayList comments = new ArrayList(); - boolean lastWasNewLine = false; - for (com.gu.typesafe.config.impl.AbstractConfigNode node : document.children()) { - if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComment) { - comments.add(((com.gu.typesafe.config.impl.ConfigNodeComment) node).commentText()); - lastWasNewLine = false; - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeSingleToken) { - com.gu.typesafe.config.impl.Token t = ((com.gu.typesafe.config.impl.ConfigNodeSingleToken) node).token(); - if (com.gu.typesafe.config.impl.Tokens.isNewline(t)) { - lineNumber++; - if (lastWasNewLine && result == null) { - comments.clear(); - } else if (result != null) { - result = result.withOrigin(result.origin().appendComments(new ArrayList(comments))); - comments.clear(); - break; - } - lastWasNewLine = true; - } - } else if (node instanceof com.gu.typesafe.config.impl.ConfigNodeComplexValue) { - result = parseValue((com.gu.typesafe.config.impl.ConfigNodeComplexValue)node, comments); - lastWasNewLine = false; - } - } - return result; - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigReference.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigReference.java deleted file mode 100644 index 5aadabe85e0..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigReference.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.Collection; -import java.util.Collections; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValue; -import com.gu.typesafe.config.ConfigValueType; -import com.gu.typesafe.config.ConfigRenderOptions; - -/** - * ConfigReference replaces ConfigReference (the older class kept for back - * compat) and represents the ${} substitution syntax. It can resolve to any - * kind of value. - */ -final class ConfigReference extends com.gu.typesafe.config.impl.AbstractConfigValue implements com.gu.typesafe.config.impl.Unmergeable { - - final private com.gu.typesafe.config.impl.SubstitutionExpression expr; - // the length of any prefixes added with relativized() - final private int prefixLength; - - ConfigReference(com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.impl.SubstitutionExpression expr) { - this(origin, expr, 0); - } - - private ConfigReference(com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.impl.SubstitutionExpression expr, int prefixLength) { - super(origin); - this.expr = expr; - this.prefixLength = prefixLength; - } - - private com.gu.typesafe.config.ConfigException.NotResolved notResolved() { - return new com.gu.typesafe.config.ConfigException.NotResolved( - "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " - + this); - } - - @Override - public ConfigValueType valueType() { - throw notResolved(); - } - - @Override - public Object unwrapped() { - throw notResolved(); - } - - @Override - protected ConfigReference newCopy(ConfigOrigin newOrigin) { - return new ConfigReference(newOrigin, expr, prefixLength); - } - - @Override - protected boolean ignoresFallbacks() { - return false; - } - - @Override - public Collection unmergedValues() { - return Collections.singleton(this); - } - - // ConfigReference should be a firewall against NotPossibleToResolve going - // further up the stack; it should convert everything to ConfigException. - // This way it's impossible for NotPossibleToResolve to "escape" since - // any failure to resolve has to start with a ConfigReference. - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) { - com.gu.typesafe.config.impl.ResolveContext newContext = context.addCycleMarker(this); - com.gu.typesafe.config.impl.AbstractConfigValue v; - try { - com.gu.typesafe.config.impl.ResolveSource.ResultWithPath resultWithPath = source.lookupSubst(newContext, expr, prefixLength); - newContext = resultWithPath.result.context; - - if (resultWithPath.result.value != null) { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "recursively resolving " + resultWithPath - + " which was the resolution of " + expr + " against " + source); - - com.gu.typesafe.config.impl.ResolveSource recursiveResolveSource = (new com.gu.typesafe.config.impl.ResolveSource( - (AbstractConfigObject) resultWithPath.pathFromRoot.last(), resultWithPath.pathFromRoot)); - - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - com.gu.typesafe.config.impl.ConfigImpl.trace(newContext.depth(), "will recursively resolve against " + recursiveResolveSource); - - com.gu.typesafe.config.impl.ResolveResult result = newContext.resolve(resultWithPath.result.value, - recursiveResolveSource); - v = result.value; - newContext = result.context; - } else { - ConfigValue fallback = context.options().getResolver().lookup(expr.path().render()); - v = (com.gu.typesafe.config.impl.AbstractConfigValue) fallback; - } - } catch (NotPossibleToResolve e) { - if (com.gu.typesafe.config.impl.ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(newContext.depth(), - "not possible to resolve " + expr + ", cycle involved: " + e.traceString()); - if (expr.optional()) - v = null; - else - throw new com.gu.typesafe.config.ConfigException.UnresolvedSubstitution(origin(), expr - + " was part of a cycle of substitutions involving " + e.traceString(), e); - } - - if (v == null && !expr.optional()) { - if (newContext.options().getAllowUnresolved()) - return com.gu.typesafe.config.impl.ResolveResult.make(newContext.removeCycleMarker(this), this); - else - throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); - } else { - return com.gu.typesafe.config.impl.ResolveResult.make(newContext.removeCycleMarker(this), v); - } - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.UNRESOLVED; - } - - // when you graft a substitution into another object, - // you have to prefix it with the location in that object - // where you grafted it; but save prefixLength so - // system property and env variable lookups don't get - // broken. - @Override - ConfigReference relativized(Path prefix) { - com.gu.typesafe.config.impl.SubstitutionExpression newExpr = expr.changePath(expr.path().prepend(prefix)); - return new ConfigReference(origin(), newExpr, prefixLength + prefix.length()); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof ConfigReference; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof ConfigReference) { - return canEqual(other) && this.expr.equals(((ConfigReference) other).expr); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - return expr.hashCode(); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - sb.append(expr.toString()); - } - - com.gu.typesafe.config.impl.SubstitutionExpression expression() { - return expr; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigString.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigString.java deleted file mode 100644 index 5eb3d55cf1a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ConfigString.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; - -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; -import com.gu.typesafe.config.ConfigRenderOptions; - -abstract class ConfigString extends com.gu.typesafe.config.impl.AbstractConfigValue implements Serializable { - - private static final long serialVersionUID = 2L; - - final protected String value; - - protected ConfigString(com.gu.typesafe.config.ConfigOrigin origin, String value) { - super(origin); - this.value = value; - } - - - final static class Quoted extends ConfigString { - Quoted(com.gu.typesafe.config.ConfigOrigin origin, String value) { - super(origin, value); - } - @Override - protected Quoted newCopy(com.gu.typesafe.config.ConfigOrigin origin) { - return new Quoted(origin, value); - } - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } - } - - // this is sort of a hack; we want to preserve whether whitespace - // was quoted until we process substitutions, so we can ignore - // unquoted whitespace when concatenating lists or objects. - // We dump this distinction when serializing and deserializing, - // but that's OK because it isn't in equals/hashCode, and we - // don't allow serializing unresolved objects which is where - // quoted-ness matters. If we later make ConfigOrigin point - // to the original token range, we could use that to implement - // wasQuoted() - final static class Unquoted extends ConfigString { - Unquoted(com.gu.typesafe.config.ConfigOrigin origin, String value) { - super(origin, value); - } - @Override - protected Unquoted newCopy(ConfigOrigin origin) { - return new Unquoted(origin, value); - } - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } - } - - boolean wasQuoted() { - return (this instanceof Quoted); - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.STRING; - } - - @Override - public String unwrapped() { - return value; - } - - @Override - String transformToString() { - return value; - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - String rendered; - if (options.getJson()) - rendered = ConfigImplUtil.renderJsonString(value); - else - rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); - sb.append(rendered); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Container.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Container.java deleted file mode 100644 index 78fc0490047..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Container.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (C) 2014 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigValue; - -/** - * An AbstractConfigValue which contains other values. Java has no way to - * express "this has to be an AbstractConfigValue also" other than making - * AbstractConfigValue an interface which would be aggravating. But we can say - * we are a ConfigValue. - */ -interface Container extends ConfigValue { - /** - * Replace a child of this value. CAUTION if replacement is null, delete the - * child, which may also delete the parent, or make the parent into a - * non-container. - */ - com.gu.typesafe.config.impl.AbstractConfigValue replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement); - - /** - * Super-expensive full traversal to see if descendant is anywhere - * underneath this container. - */ - boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/DefaultTransformer.java b/scripts/src/main/java/com/gu/typesafe/config/impl/DefaultTransformer.java deleted file mode 100644 index 8845513c63a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/DefaultTransformer.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -import com.gu.typesafe.config.ConfigValueType; - -/** - * Default automatic type transformations. - */ -final class DefaultTransformer { - - static com.gu.typesafe.config.impl.AbstractConfigValue transform(com.gu.typesafe.config.impl.AbstractConfigValue value, - com.gu.typesafe.config.ConfigValueType requested) { - if (value.valueType() == com.gu.typesafe.config.ConfigValueType.STRING) { - String s = (String) value.unwrapped(); - switch (requested) { - case NUMBER: - try { - Long v = Long.parseLong(s); - return new com.gu.typesafe.config.impl.ConfigLong(value.origin(), v, s); - } catch (NumberFormatException e) { - // try Double - } - try { - Double v = Double.parseDouble(s); - return new com.gu.typesafe.config.impl.ConfigDouble(value.origin(), v, s); - } catch (NumberFormatException e) { - // oh well. - } - break; - case NULL: - if (s.equals("null")) - return new com.gu.typesafe.config.impl.ConfigNull(value.origin()); - break; - case BOOLEAN: - if (s.equals("true") || s.equals("yes") || s.equals("on")) { - return new com.gu.typesafe.config.impl.ConfigBoolean(value.origin(), true); - } else if (s.equals("false") || s.equals("no") - || s.equals("off")) { - return new com.gu.typesafe.config.impl.ConfigBoolean(value.origin(), false); - } - break; - case LIST: - // can't go STRING to LIST automatically - break; - case OBJECT: - // can't go STRING to OBJECT automatically - break; - case STRING: - // no-op STRING to STRING - break; - } - } else if (requested == com.gu.typesafe.config.ConfigValueType.STRING) { - // if we converted null to string here, then you wouldn't properly - // get a missing-value error if you tried to get a null value - // as a string. - switch (value.valueType()) { - case NUMBER: // FALL THROUGH - case BOOLEAN: - return new com.gu.typesafe.config.impl.ConfigString.Quoted(value.origin(), - value.transformToString()); - case NULL: - // want to be sure this throws instead of returning "null" as a - // string - break; - case OBJECT: - // no OBJECT to STRING automatically - break; - case LIST: - // no LIST to STRING automatically - break; - case STRING: - // no-op STRING to STRING - break; - } - } else if (requested == com.gu.typesafe.config.ConfigValueType.LIST && value.valueType() == ConfigValueType.OBJECT) { - // attempt to convert an array-like (numeric indices) object to a - // list. This would be used with .properties syntax for example: - // -Dfoo.0=bar -Dfoo.1=baz - // To ensure we still throw type errors for objects treated - // as lists in most cases, we'll refuse to convert if the object - // does not contain any numeric keys. This means we don't allow - // empty objects here though :-/ - AbstractConfigObject o = (AbstractConfigObject) value; - Map values = new HashMap(); - for (String key : o.keySet()) { - int i; - try { - i = Integer.parseInt(key, 10); - if (i < 0) - continue; - values.put(i, o.get(key)); - } catch (NumberFormatException e) { - continue; - } - } - if (!values.isEmpty()) { - ArrayList> entryList = new ArrayList>( - values.entrySet()); - // sort by numeric index - Collections.sort(entryList, - new Comparator>() { - @Override - public int compare(Map.Entry a, - Map.Entry b) { - return Integer.compare(a.getKey(), b.getKey()); - } - }); - // drop the indices (we allow gaps in the indices, for better or - // worse) - ArrayList list = new ArrayList(); - for (Map.Entry entry : entryList) { - list.add(entry.getValue()); - } - return new com.gu.typesafe.config.impl.SimpleConfigList(value.origin(), list); - } - } - - return value; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/FromMapMode.java b/scripts/src/main/java/com/gu/typesafe/config/impl/FromMapMode.java deleted file mode 100644 index 83036c184c6..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/FromMapMode.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -enum FromMapMode { - KEYS_ARE_PATHS, KEYS_ARE_KEYS -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/FullIncluder.java b/scripts/src/main/java/com/gu/typesafe/config/impl/FullIncluder.java deleted file mode 100644 index d4c3c80d74d..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/FullIncluder.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigIncluder; -import com.gu.typesafe.config.ConfigIncluderFile; -import com.gu.typesafe.config.ConfigIncluderURL; -import com.gu.typesafe.config.ConfigIncluderClasspath; - -interface FullIncluder extends ConfigIncluder, ConfigIncluderFile, ConfigIncluderURL, - ConfigIncluderClasspath { - -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/MemoKey.java b/scripts/src/main/java/com/gu/typesafe/config/impl/MemoKey.java deleted file mode 100644 index b9068b5918a..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/MemoKey.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.gu.typesafe.config.impl; - -/** The key used to memoize already-traversed nodes when resolving substitutions */ -final class MemoKey { - MemoKey(com.gu.typesafe.config.impl.AbstractConfigValue value, Path restrictToChildOrNull) { - this.value = value; - this.restrictToChildOrNull = restrictToChildOrNull; - } - - final private com.gu.typesafe.config.impl.AbstractConfigValue value; - final private Path restrictToChildOrNull; - - @Override - public final int hashCode() { - int h = System.identityHashCode(value); - if (restrictToChildOrNull != null) { - return h + 41 * (41 + restrictToChildOrNull.hashCode()); - } else { - return h; - } - } - - @Override - public final boolean equals(Object other) { - if (other instanceof MemoKey) { - MemoKey o = (MemoKey) other; - if (o.value != this.value) - return false; - else if (o.restrictToChildOrNull == this.restrictToChildOrNull) - return true; - else if (o.restrictToChildOrNull == null || this.restrictToChildOrNull == null) - return false; - else - return o.restrictToChildOrNull.equals(this.restrictToChildOrNull); - } else { - return false; - } - } - - @Override - public final String toString() { - return "MemoKey(" + value + "@" + System.identityHashCode(value) + "," + restrictToChildOrNull + ")"; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/MergeableValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/MergeableValue.java deleted file mode 100644 index 007b5b13408..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/MergeableValue.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigValue; -import com.gu.typesafe.config.ConfigMergeable; - -interface MergeableValue extends ConfigMergeable { - // converts a Config to its root object and a ConfigValue to itself - ConfigValue toFallbackValue(); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/OriginType.java b/scripts/src/main/java/com/gu/typesafe/config/impl/OriginType.java deleted file mode 100644 index aae0d42eb64..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/OriginType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.gu.typesafe.config.impl; - -// caution: ordinals used in serialization -enum OriginType { - GENERIC, - FILE, - URL, - RESOURCE -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Parseable.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Parseable.java deleted file mode 100644 index 1a82c834b66..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Parseable.java +++ /dev/null @@ -1,873 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FilterReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringReader; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.util.*; - -import com.gu.typesafe.config.ConfigParseOptions; -import com.gu.typesafe.config.parser.ConfigDocument; - -/** - * Internal implementation detail, not ABI stable, do not touch. - * For use only by the {@link com.typesafe.config} package. - * The point of this class is to avoid "propagating" each - * overload on "thing which can be parsed" through multiple - * interfaces. Most interfaces can have just one overload that - * takes a Parseable. Also it's used as an abstract "resource - * handle" in the ConfigIncluder interface. - */ -public abstract class Parseable implements com.gu.typesafe.config.ConfigParseable { - private com.gu.typesafe.config.ConfigIncludeContext includeContext; - private com.gu.typesafe.config.ConfigParseOptions initialOptions; - private com.gu.typesafe.config.ConfigOrigin initialOrigin; - - /** - * Internal implementation detail, not ABI stable, do not touch. - */ - protected interface Relativizer { - com.gu.typesafe.config.ConfigParseable relativeTo(String filename); - } - - private static final ThreadLocal> parseStack = new ThreadLocal>() { - @Override - protected LinkedList initialValue() { - return new LinkedList(); - } - }; - - private static final int MAX_INCLUDE_DEPTH = 50; - - protected Parseable() { - } - - private com.gu.typesafe.config.ConfigParseOptions fixupOptions(com.gu.typesafe.config.ConfigParseOptions baseOptions) { - com.gu.typesafe.config.ConfigSyntax syntax = baseOptions.getSyntax(); - if (syntax == null) { - syntax = guessSyntax(); - } - if (syntax == null) { - syntax = com.gu.typesafe.config.ConfigSyntax.CONF; - } - com.gu.typesafe.config.ConfigParseOptions modified = baseOptions.setSyntax(syntax); - - // make sure the app-provided includer falls back to default - modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); - // make sure the app-provided includer is complete - modified = modified.setIncluder(SimpleIncluder.makeFull(modified.getIncluder())); - - return modified; - } - - protected void postConstruct(com.gu.typesafe.config.ConfigParseOptions baseOptions) { - this.initialOptions = fixupOptions(baseOptions); - - this.includeContext = new SimpleIncludeContext(this); - - if (initialOptions.getOriginDescription() != null) - initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription()); - else - initialOrigin = createOrigin(); - } - - // the general idea is that any work should be in here, not in the - // constructor, so that exceptions are thrown from the public parse() - // function and not from the creation of the Parseable. - // Essentially this is a lazy field. The parser should close the - // reader when it's done with it. - // ALSO, IMPORTANT: if the file or URL is not found, this must throw. - // to support the "allow missing" feature. - protected abstract Reader reader() throws IOException; - - protected Reader reader(com.gu.typesafe.config.ConfigParseOptions options) throws IOException { - return reader(); - } - - protected static void trace(String message) { - if (ConfigImpl.traceLoadsEnabled()) { - ConfigImpl.trace(message); - } - } - - com.gu.typesafe.config.ConfigSyntax guessSyntax() { - return null; - } - - com.gu.typesafe.config.ConfigSyntax contentType() { - return null; - } - - com.gu.typesafe.config.ConfigParseable relativeTo(String filename) { - // fall back to classpath; we treat the "filename" as absolute - // (don't add a package name in front), - // if it starts with "/" then remove the "/", for consistency - // with ParseableResources.relativeTo - String resource = filename; - if (filename.startsWith("/")) - resource = filename.substring(1); - return newResources(resource, options().setOriginDescription(null)); - } - - com.gu.typesafe.config.ConfigIncludeContext includeContext() { - return includeContext; - } - - static AbstractConfigObject forceParsedToObject(com.gu.typesafe.config.ConfigValue value) { - if (value instanceof AbstractConfigObject) { - return (AbstractConfigObject) value; - } else { - throw new com.gu.typesafe.config.ConfigException.WrongType(value.origin(), "", "object at file root", value - .valueType().name()); - } - } - - @Override - public com.gu.typesafe.config.ConfigObject parse(com.gu.typesafe.config.ConfigParseOptions baseOptions) { - - LinkedList stack = parseStack.get(); - if (stack.size() >= MAX_INCLUDE_DEPTH) { - throw new com.gu.typesafe.config.ConfigException.Parse(initialOrigin, "include statements nested more than " - + MAX_INCLUDE_DEPTH - + " times, you probably have a cycle in your includes. Trace: " + stack); - } - - stack.addFirst(this); - try { - return forceParsedToObject(parseValue(baseOptions)); - } finally { - stack.removeFirst(); - if (stack.isEmpty()) { - parseStack.remove(); - } - } - } - - final com.gu.typesafe.config.impl.AbstractConfigValue parseValue(com.gu.typesafe.config.ConfigParseOptions baseOptions) { - // note that we are NOT using our "initialOptions", - // but using the ones from the passed-in options. The idea is that - // callers can get our original options and then parse with different - // ones if they want. - com.gu.typesafe.config.ConfigParseOptions options = fixupOptions(baseOptions); - - // passed-in options can override origin - com.gu.typesafe.config.ConfigOrigin origin; - if (options.getOriginDescription() != null) - origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); - else - origin = initialOrigin; - return parseValue(origin, options); - } - - final private com.gu.typesafe.config.impl.AbstractConfigValue parseValue(com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) { - try { - return rawParseValue(origin, finalOptions); - } catch (IOException e) { - if (finalOptions.getAllowMissing()) { - trace(e.getMessage() + ". Allowing Missing File, this can be turned off by setting" + - " ConfigParseOptions.allowMissing = false"); - return SimpleConfigObject.emptyMissing(origin); - } else { - trace("exception loading " + origin.description() + ": " + e.getClass().getName() - + ": " + e.getMessage()); - throw new com.gu.typesafe.config.ConfigException.IO(origin, - e.getClass().getName() + ": " + e.getMessage(), e); - } - } - } - - final com.gu.typesafe.config.parser.ConfigDocument parseDocument(com.gu.typesafe.config.ConfigParseOptions baseOptions) { - // note that we are NOT using our "initialOptions", - // but using the ones from the passed-in options. The idea is that - // callers can get our original options and then parse with different - // ones if they want. - com.gu.typesafe.config.ConfigParseOptions options = fixupOptions(baseOptions); - - // passed-in options can override origin - com.gu.typesafe.config.ConfigOrigin origin; - if (options.getOriginDescription() != null) - origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); - else - origin = initialOrigin; - return parseDocument(origin, options); - } - - final private com.gu.typesafe.config.parser.ConfigDocument parseDocument(com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) { - try { - return rawParseDocument(origin, finalOptions); - } catch (IOException e) { - if (finalOptions.getAllowMissing()) { - ArrayList children = new ArrayList(); - children.add(new ConfigNodeObject(new ArrayList())); - return new SimpleConfigDocument(new ConfigNodeRoot(children, origin), finalOptions); - } else { - trace("exception loading " + origin.description() + ": " + e.getClass().getName() - + ": " + e.getMessage()); - throw new com.gu.typesafe.config.ConfigException.IO(origin, - e.getClass().getName() + ": " + e.getMessage(), e); - } - } - } - - // this is parseValue without post-processing the IOException or handling - // options.getAllowMissing() - protected com.gu.typesafe.config.impl.AbstractConfigValue rawParseValue(com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.ConfigParseOptions finalOptions) - throws IOException { - Reader reader = reader(finalOptions); - - // after reader() we will have loaded the Content-Type. - com.gu.typesafe.config.ConfigSyntax contentType = contentType(); - - com.gu.typesafe.config.ConfigParseOptions optionsWithContentType; - if (contentType != null) { - if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) - trace("Overriding syntax " + finalOptions.getSyntax() - + " with Content-Type which specified " + contentType); - - optionsWithContentType = finalOptions.setSyntax(contentType); - } else { - optionsWithContentType = finalOptions; - } - - try { - return rawParseValue(reader, origin, optionsWithContentType); - } finally { - reader.close(); - } - } - - private com.gu.typesafe.config.impl.AbstractConfigValue rawParseValue(Reader reader, com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) throws IOException { - if (finalOptions.getSyntax() == com.gu.typesafe.config.ConfigSyntax.PROPERTIES) { - return PropertiesParser.parse(reader, origin); - } else { - Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); - ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions); - return ConfigParser.parse(document, origin, finalOptions, includeContext()); - } - } - - // this is parseDocument without post-processing the IOException or handling - // options.getAllowMissing() - protected com.gu.typesafe.config.parser.ConfigDocument rawParseDocument(com.gu.typesafe.config.ConfigOrigin origin, com.gu.typesafe.config.ConfigParseOptions finalOptions) - throws IOException { - Reader reader = reader(finalOptions); - - // after reader() we will have loaded the Content-Type. - com.gu.typesafe.config.ConfigSyntax contentType = contentType(); - - com.gu.typesafe.config.ConfigParseOptions optionsWithContentType; - if (contentType != null) { - if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) - trace("Overriding syntax " + finalOptions.getSyntax() - + " with Content-Type which specified " + contentType); - - optionsWithContentType = finalOptions.setSyntax(contentType); - } else { - optionsWithContentType = finalOptions; - } - - try { - return rawParseDocument(reader, origin, optionsWithContentType); - } finally { - reader.close(); - } - } - - private com.gu.typesafe.config.parser.ConfigDocument rawParseDocument(Reader reader, com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) throws IOException { - Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); - return new SimpleConfigDocument(ConfigDocumentParser.parse(tokens, origin, finalOptions), finalOptions); - } - - public com.gu.typesafe.config.ConfigObject parse() { - return forceParsedToObject(parseValue(options())); - } - - public ConfigDocument parseConfigDocument() { - return parseDocument(options()); - } - - com.gu.typesafe.config.impl.AbstractConfigValue parseValue() { - return parseValue(options()); - } - - @Override - public final com.gu.typesafe.config.ConfigOrigin origin() { - return initialOrigin; - } - - protected abstract com.gu.typesafe.config.ConfigOrigin createOrigin(); - - @Override - public com.gu.typesafe.config.ConfigParseOptions options() { - return initialOptions; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - private static Reader readerFromStream(InputStream input) { - return readerFromStream(input, "UTF-8"); - } - - private static Reader readerFromStream(InputStream input, String encoding) { - try { - // well, this is messed up. If we aren't going to close - // the passed-in InputStream then we have no way to - // close these readers. So maybe we should not have an - // InputStream version, only a Reader version. - Reader reader = new InputStreamReader(input, encoding); - return new BufferedReader(reader); - } catch (UnsupportedEncodingException e) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Java runtime does not support UTF-8", e); - } - } - - private static Reader doNotClose(Reader input) { - return new FilterReader(input) { - @Override - public void close() { - // NOTHING. - } - }; - } - - static URL relativeTo(URL url, String filename) { - // I'm guessing this completely fails on Windows, help wanted - if (new File(filename).isAbsolute()) - return null; - - try { - URI siblingURI = url.toURI(); - URI relative = new URI(filename); - - // this seems wrong, but it's documented that the last - // element of the path in siblingURI gets stripped out, - // so to get something in the same directory as - // siblingURI we just call resolve(). - URL resolved = siblingURI.resolve(relative).toURL(); - - return resolved; - } catch (MalformedURLException e) { - return null; - } catch (URISyntaxException e) { - return null; - } catch (IllegalArgumentException e) { - return null; - } - } - - static File relativeTo(File file, String filename) { - File child = new File(filename); - - if (child.isAbsolute()) - return null; - - File parent = file.getParentFile(); - - if (parent == null) - return null; - else - return new File(parent, filename); - } - - // this is a parseable that doesn't exist and just throws when you try to - // parse it - private final static class ParseableNotFound extends Parseable { - final private String what; - final private String message; - - ParseableNotFound(String what, String message, com.gu.typesafe.config.ConfigParseOptions options) { - this.what = what; - this.message = message; - postConstruct(options); - } - - @Override - protected Reader reader() throws IOException { - throw new FileNotFoundException(message); - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newSimple(what); - } - } - - public static Parseable newNotFound(String whatNotFound, String message, - com.gu.typesafe.config.ConfigParseOptions options) { - return new ParseableNotFound(whatNotFound, message, options); - } - - private final static class ParseableReader extends Parseable { - final private Reader reader; - - ParseableReader(Reader reader, com.gu.typesafe.config.ConfigParseOptions options) { - this.reader = reader; - postConstruct(options); - } - - @Override - protected Reader reader() { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from reader " + reader); - return reader; - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newSimple("Reader"); - } - } - - // note that we will never close this reader; you have to do it when parsing - // is complete. - public static Parseable newReader(Reader reader, com.gu.typesafe.config.ConfigParseOptions options) { - - return new ParseableReader(doNotClose(reader), options); - } - - private final static class ParseableString extends Parseable { - final private String input; - - ParseableString(String input, com.gu.typesafe.config.ConfigParseOptions options) { - this.input = input; - postConstruct(options); - } - - @Override - protected Reader reader() { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from a String " + input); - return new StringReader(input); - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newSimple("String"); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + input + ")"; - } - } - - public static Parseable newString(String input, com.gu.typesafe.config.ConfigParseOptions options) { - return new ParseableString(input, options); - } - - private static final String jsonContentType = "application/json"; - private static final String propertiesContentType = "text/x-java-properties"; - private static final String hoconContentType = "application/hocon"; - - private static class ParseableURL extends Parseable { - final protected URL input; - private String contentType = null; - - protected ParseableURL(URL input) { - this.input = input; - // does not postConstruct (subclass does it) - } - - ParseableURL(URL input, com.gu.typesafe.config.ConfigParseOptions options) { - this(input); - postConstruct(options); - } - - @Override - protected Reader reader() throws IOException { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("reader() without options should not be called on ParseableURL"); - } - - private static String acceptContentType(com.gu.typesafe.config.ConfigParseOptions options) { - if (options.getSyntax() == null) - return null; - - switch (options.getSyntax()) { - case JSON: - return jsonContentType; - case CONF: - return hoconContentType; - case PROPERTIES: - return propertiesContentType; - } - - // not sure this is reachable but javac thinks it is - return null; - } - - @Override - protected Reader reader(com.gu.typesafe.config.ConfigParseOptions options) throws IOException { - try { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from a URL: " + input.toExternalForm()); - URLConnection connection = input.openConnection(); - - // allow server to serve multiple types from one URL - String acceptContent = acceptContentType(options); - if (acceptContent != null) { - connection.setRequestProperty("Accept", acceptContent); - } - - connection.connect(); - - // save content type for later - contentType = connection.getContentType(); - if (contentType != null) { - if (ConfigImpl.traceLoadsEnabled()) - trace("URL sets Content-Type: '" + contentType + "'"); - contentType = contentType.trim(); - int semi = contentType.indexOf(';'); - if (semi >= 0) - contentType = contentType.substring(0, semi); - } - - InputStream stream = connection.getInputStream(); - - return readerFromStream(stream); - } catch (FileNotFoundException fnf) { - // If the resource is not found (HTTP response - // code 404 or something alike), then it's fine to - // treat it according to the allowMissing setting - // and "include" spec. But if we have something - // like HTTP 503 it seems to be better to fail - // early, because this may be a sign of broken - // environment. Java throws FileNotFoundException - // if it sees 404 or 410. - throw fnf; - } catch (IOException e) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Cannot load config from URL: " + input.toExternalForm(), e); - } - } - - @Override - com.gu.typesafe.config.ConfigSyntax guessSyntax() { - return ConfigImplUtil.syntaxFromExtension(input.getPath()); - } - - @Override - com.gu.typesafe.config.ConfigSyntax contentType() { - if (contentType != null) { - if (contentType.equals(jsonContentType)) - return com.gu.typesafe.config.ConfigSyntax.JSON; - else if (contentType.equals(propertiesContentType)) - return com.gu.typesafe.config.ConfigSyntax.PROPERTIES; - else if (contentType.equals(hoconContentType)) - return com.gu.typesafe.config.ConfigSyntax.CONF; - else { - if (ConfigImpl.traceLoadsEnabled()) - trace("'" + contentType + "' isn't a known content type"); - return null; - } - } else { - return null; - } - } - - @Override - com.gu.typesafe.config.ConfigParseable relativeTo(String filename) { - URL url = relativeTo(input, filename); - if (url == null) - return null; - return newURL(url, options().setOriginDescription(null)); - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newURL(input); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + input.toExternalForm() + ")"; - } - } - - public static Parseable newURL(URL input, com.gu.typesafe.config.ConfigParseOptions options) { - // we want file: URLs and files to always behave the same, so switch - // to a file if it's a file: URL - if (input.getProtocol().equals("file")) { - return newFile(ConfigImplUtil.urlToFile(input), options); - } else { - return new ParseableURL(input, options); - } - } - - private final static class ParseableFile extends Parseable { - final private File input; - - ParseableFile(File input, com.gu.typesafe.config.ConfigParseOptions options) { - this.input = input; - postConstruct(options); - } - - @Override - protected Reader reader() throws IOException { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from a file: " + input); - InputStream stream = new FileInputStream(input); - return readerFromStream(stream); - } - - @Override - com.gu.typesafe.config.ConfigSyntax guessSyntax() { - return ConfigImplUtil.syntaxFromExtension(input.getName()); - } - - @Override - com.gu.typesafe.config.ConfigParseable relativeTo(String filename) { - File sibling; - if ((new File(filename)).isAbsolute()) { - sibling = new File(filename); - } else { - // this may return null - sibling = relativeTo(input, filename); - } - if (sibling == null) - return null; - if (sibling.exists()) { - trace(sibling + " exists, so loading it as a file"); - return newFile(sibling, options().setOriginDescription(null)); - } else { - trace(sibling + " does not exist, so trying it as a classpath resource"); - return super.relativeTo(filename); - } - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newFile(input.getPath()); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + input.getPath() + ")"; - } - } - - public static Parseable newFile(File input, com.gu.typesafe.config.ConfigParseOptions options) { - return new ParseableFile(input, options); - } - - - private final static class ParseableResourceURL extends ParseableURL { - - private final Relativizer relativizer; - private final String resource; - - ParseableResourceURL(URL input, com.gu.typesafe.config.ConfigParseOptions options, String resource, Relativizer relativizer) { - super(input); - this.relativizer = relativizer; - this.resource = resource; - postConstruct(options); - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newResource(resource, input); - } - - @Override - com.gu.typesafe.config.ConfigParseable relativeTo(String filename) { - return relativizer.relativeTo(filename); - } - } - - private static Parseable newResourceURL(URL input, com.gu.typesafe.config.ConfigParseOptions options, String resource, Relativizer relativizer) { - return new ParseableResourceURL(input, options, resource, relativizer); - } - - private final static class ParseableResources extends Parseable implements Relativizer { - final private String resource; - - ParseableResources(String resource, com.gu.typesafe.config.ConfigParseOptions options) { - this.resource = resource; - postConstruct(options); - } - - @Override - protected Reader reader() throws IOException { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("reader() should not be called on resources"); - } - - @Override - protected AbstractConfigObject rawParseValue(com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) throws IOException { - ClassLoader loader = finalOptions.getClassLoader(); - if (loader == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); - Enumeration e = loader.getResources(resource); - if (!e.hasMoreElements()) { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from class loader " + loader - + " but there were no resources called " + resource); - throw new IOException("resource not found on classpath: " + resource); - } - AbstractConfigObject merged = SimpleConfigObject.empty(origin); - while (e.hasMoreElements()) { - URL url = e.nextElement(); - - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from resource '" + resource + "' URL " + url.toExternalForm() + " from class loader " - + loader); - - Parseable element = newResourceURL(url, finalOptions, resource, this); - - com.gu.typesafe.config.impl.AbstractConfigValue v = element.parseValue(); - - merged = merged.withFallback(v); - } - - return merged; - } - - @Override - com.gu.typesafe.config.ConfigSyntax guessSyntax() { - return ConfigImplUtil.syntaxFromExtension(resource); - } - - static String parent(String resource) { - // the "resource" is not supposed to begin with a "/" - // because it's supposed to be the raw resource - // (ClassLoader#getResource), not the - // resource "syntax" (Class#getResource) - int i = resource.lastIndexOf('/'); - if (i < 0) { - return null; - } else { - return resource.substring(0, i); - } - } - - @Override - public com.gu.typesafe.config.ConfigParseable relativeTo(String sibling) { - if (sibling.startsWith("/")) { - // if it starts with "/" then don't make it relative to - // the including resource - return newResources(sibling.substring(1), options().setOriginDescription(null)); - } else { - // here we want to build a new resource name and let - // the class loader have it, rather than getting the - // url with getResource() and relativizing to that url. - // This is needed in case the class loader is going to - // search a classpath. - String parent = parent(resource); - if (parent == null) - return newResources(sibling, options().setOriginDescription(null)); - else - return newResources(parent + "/" + sibling, options() - .setOriginDescription(null)); - } - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newResource(resource); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + resource + ")"; - } - } - - public static Parseable newResources(Class klass, String resource, com.gu.typesafe.config.ConfigParseOptions options) { - return newResources(convertResourceName(klass, resource), - options.setClassLoader(klass.getClassLoader())); - } - - // this function is supposed to emulate the difference - // between Class.getResource and ClassLoader.getResource - // (unfortunately there doesn't seem to be public API for it). - // We're using it because the Class API is more limited, - // for example it lacks getResources(). So we want to be able to - // use ClassLoader directly. - private static String convertResourceName(Class klass, String resource) { - if (resource.startsWith("/")) { - // "absolute" resource, chop the slash - return resource.substring(1); - } else { - String className = klass.getName(); - int i = className.lastIndexOf('.'); - if (i < 0) { - // no package - return resource; - } else { - // need to be relative to the package - String packageName = className.substring(0, i); - String packagePath = packageName.replace('.', '/'); - return packagePath + "/" + resource; - } - } - } - - public static Parseable newResources(String resource, com.gu.typesafe.config.ConfigParseOptions options) { - if (options.getClassLoader() == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); - return new ParseableResources(resource, options); - } - - private final static class ParseableProperties extends Parseable { - final private Properties props; - - ParseableProperties(Properties props, com.gu.typesafe.config.ConfigParseOptions options) { - this.props = props; - postConstruct(options); - } - - @Override - protected Reader reader() throws IOException { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("reader() should not be called on props"); - } - - @Override - protected AbstractConfigObject rawParseValue(com.gu.typesafe.config.ConfigOrigin origin, - com.gu.typesafe.config.ConfigParseOptions finalOptions) { - if (ConfigImpl.traceLoadsEnabled()) - trace("Loading config from properties " + props); - return PropertiesParser.fromProperties(origin, props); - } - - @Override - com.gu.typesafe.config.ConfigSyntax guessSyntax() { - return com.gu.typesafe.config.ConfigSyntax.PROPERTIES; - } - - @Override - protected com.gu.typesafe.config.ConfigOrigin createOrigin() { - return SimpleConfigOrigin.newSimple("properties"); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "(" + props.size() + " props)"; - } - } - - public static Parseable newProperties(Properties properties, ConfigParseOptions options) { - return new ParseableProperties(properties, options); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Path.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Path.java deleted file mode 100644 index e0259a5e10f..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Path.java +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.*; - -import com.gu.typesafe.config.ConfigException; - -final class Path { - - final private String first; - final private Path remainder; - - Path(String first, Path remainder) { - this.first = first; - this.remainder = remainder; - } - - Path(String... elements) { - if (elements.length == 0) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("empty path"); - this.first = elements[0]; - if (elements.length > 1) { - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - for (int i = 1; i < elements.length; ++i) { - pb.appendKey(elements[i]); - } - this.remainder = pb.result(); - } else { - this.remainder = null; - } - } - - // append all the paths in the list together into one path - Path(List pathsToConcat) { - this(pathsToConcat.iterator()); - } - - // append all the paths in the iterator together into one path - Path(Iterator i) { - if (!i.hasNext()) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("empty path"); - - Path firstPath = i.next(); - this.first = firstPath.first; - - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - if (firstPath.remainder != null) { - pb.appendPath(firstPath.remainder); - } - while (i.hasNext()) { - pb.appendPath(i.next()); - } - this.remainder = pb.result(); - } - - String first() { - return first; - } - - /** - * - * @return path minus the first element or null if no more elements - */ - Path remainder() { - return remainder; - } - - /** - * - * @return path minus the last element or null if we have just one element - */ - Path parent() { - if (remainder == null) - return null; - - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - Path p = this; - while (p.remainder != null) { - pb.appendKey(p.first); - p = p.remainder; - } - return pb.result(); - } - - /** - * - * @return last element in the path - */ - String last() { - Path p = this; - while (p.remainder != null) { - p = p.remainder; - } - return p.first; - } - - Path prepend(Path toPrepend) { - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - pb.appendPath(toPrepend); - pb.appendPath(this); - return pb.result(); - } - - int length() { - int count = 1; - Path p = remainder; - while (p != null) { - count += 1; - p = p.remainder; - } - return count; - } - - Path subPath(int removeFromFront) { - int count = removeFromFront; - Path p = this; - while (p != null && count > 0) { - count -= 1; - p = p.remainder; - } - return p; - } - - Path subPath(int firstIndex, int lastIndex) { - if (lastIndex < firstIndex) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("bad call to subPath"); - - Path from = subPath(firstIndex); - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - int count = lastIndex - firstIndex; - while (count > 0) { - count -= 1; - pb.appendKey(from.first()); - from = from.remainder(); - if (from == null) - throw new ConfigException.BugOrBroken("subPath lastIndex out of range " + lastIndex); - } - return pb.result(); - } - - boolean startsWith(Path other) { - Path myRemainder = this; - Path otherRemainder = other; - if (otherRemainder.length() <= myRemainder.length()) { - while(otherRemainder != null) { - if (!otherRemainder.first().equals(myRemainder.first())) - return false; - myRemainder = myRemainder.remainder(); - otherRemainder = otherRemainder.remainder(); - } - return true; - } - return false; - } - - @Override - public boolean equals(Object other) { - if (other instanceof Path) { - Path that = (Path) other; - return this.first.equals(that.first) - && com.gu.typesafe.config.impl.ConfigImplUtil.equalsHandlingNull(this.remainder, - that.remainder); - } else { - return false; - } - } - - @Override - public int hashCode() { - return 41 * (41 + first.hashCode()) - + (remainder == null ? 0 : remainder.hashCode()); - } - - // this doesn't have a very precise meaning, just to reduce - // noise from quotes in the rendered path for average cases - static boolean hasFunkyChars(String s) { - int length = s.length(); - - if (length == 0) - return false; - - for (int i = 0; i < length; ++i) { - char c = s.charAt(i); - - if (Character.isLetterOrDigit(c) || c == '-' || c == '_') - continue; - else - return true; - } - return false; - } - - private void appendToStringBuilder(StringBuilder sb) { - if (hasFunkyChars(first) || first.isEmpty()) - sb.append(ConfigImplUtil.renderJsonString(first)); - else - sb.append(first); - if (remainder != null) { - sb.append("."); - remainder.appendToStringBuilder(sb); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Path("); - appendToStringBuilder(sb); - sb.append(")"); - return sb.toString(); - } - - /** - * toString() is a debugging-oriented version while this is an - * error-message-oriented human-readable one. - */ - String render() { - StringBuilder sb = new StringBuilder(); - appendToStringBuilder(sb); - return sb.toString(); - } - - static Path newKey(String key) { - return new Path(key, null); - } - - static Path newPath(String path) { - return com.gu.typesafe.config.impl.PathParser.parsePath(path); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/PathBuilder.java b/scripts/src/main/java/com/gu/typesafe/config/impl/PathBuilder.java deleted file mode 100644 index 1989d695ac1..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/PathBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; - -import java.util.Stack; - -final class PathBuilder { - // the keys are kept "backward" (top of stack is end of path) - final private Stack keys; - private Path result; - - PathBuilder() { - keys = new Stack(); - } - - private void checkCanAppend() { - if (result != null) - throw new ConfigException.BugOrBroken( - "Adding to PathBuilder after getting result"); - } - - void appendKey(String key) { - checkCanAppend(); - - keys.push(key); - } - - void appendPath(Path path) { - checkCanAppend(); - - String first = path.first(); - Path remainder = path.remainder(); - while (true) { - keys.push(first); - if (remainder != null) { - first = remainder.first(); - remainder = remainder.remainder(); - } else { - break; - } - } - } - - Path result() { - // note: if keys is empty, we want to return null, which is a valid - // empty path - if (result == null) { - Path remainder = null; - while (!keys.isEmpty()) { - String key = keys.pop(); - remainder = new Path(key, remainder); - } - result = remainder; - } - return result; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/PathParser.java b/scripts/src/main/java/com/gu/typesafe/config/impl/PathParser.java deleted file mode 100644 index e78e59debd0..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/PathParser.java +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigSyntax; -import com.gu.typesafe.config.ConfigValueType; - -import java.io.StringReader; -import java.util.*; - -final class PathParser { - static class Element { - StringBuilder sb; - // an element can be empty if it has a quoted empty string "" in it - boolean canBeEmpty; - - Element(String initial, boolean canBeEmpty) { - this.canBeEmpty = canBeEmpty; - this.sb = new StringBuilder(initial); - } - - @Override - public String toString() { - return "Element(" + sb.toString() + "," + canBeEmpty + ")"; - } - } - - static com.gu.typesafe.config.ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter"); - - static ConfigNodePath parsePathNode(String path) { - return parsePathNode(path, com.gu.typesafe.config.ConfigSyntax.CONF); - } - - static ConfigNodePath parsePathNode(String path, com.gu.typesafe.config.ConfigSyntax flavor) { - StringReader reader = new StringReader(path); - - try { - Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, - flavor); - tokens.next(); // drop START - return parsePathNodeExpression(tokens, apiOrigin, path, flavor); - } finally { - reader.close(); - } - } - - static Path parsePath(String path) { - Path speculated = speculativeFastParsePath(path); - if (speculated != null) - return speculated; - - StringReader reader = new StringReader(path); - - try { - Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, - com.gu.typesafe.config.ConfigSyntax.CONF); - tokens.next(); // drop START - return parsePathExpression(tokens, apiOrigin, path); - } finally { - reader.close(); - } - } - - protected static Path parsePathExpression(Iterator expression, - com.gu.typesafe.config.ConfigOrigin origin) { - return parsePathExpression(expression, origin, null, null, com.gu.typesafe.config.ConfigSyntax.CONF); - } - - protected static Path parsePathExpression(Iterator expression, - com.gu.typesafe.config.ConfigOrigin origin, String originalText) { - return parsePathExpression(expression, origin, originalText, null, com.gu.typesafe.config.ConfigSyntax.CONF); - } - - protected static ConfigNodePath parsePathNodeExpression(Iterator expression, - com.gu.typesafe.config.ConfigOrigin origin) { - return parsePathNodeExpression(expression, origin, null, com.gu.typesafe.config.ConfigSyntax.CONF); - } - - protected static ConfigNodePath parsePathNodeExpression(Iterator expression, - com.gu.typesafe.config.ConfigOrigin origin, String originalText, com.gu.typesafe.config.ConfigSyntax flavor) { - ArrayList pathTokens = new ArrayList(); - Path path = parsePathExpression(expression, origin, originalText, pathTokens, flavor); - return new ConfigNodePath(path, pathTokens); - } - - // originalText may be null if not available - protected static Path parsePathExpression(Iterator expression, - ConfigOrigin origin, String originalText, - ArrayList pathTokens, - com.gu.typesafe.config.ConfigSyntax flavor) { - // each builder in "buf" is an element in the path. - List buf = new ArrayList(); - buf.add(new Element("", false)); - - if (!expression.hasNext()) { - throw new com.gu.typesafe.config.ConfigException.BadPath(origin, originalText, - "Expecting a field name or path here, but got nothing"); - } - - while (expression.hasNext()) { - Token t = expression.next(); - - if (pathTokens != null) - pathTokens.add(t); - - // Ignore all IgnoredWhitespace tokens - if (Tokens.isIgnoredWhitespace(t)) - continue; - - if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { - com.gu.typesafe.config.impl.AbstractConfigValue v = Tokens.getValue(t); - // this is a quoted string; so any periods - // in here don't count as path separators - String s = v.transformToString(); - - addPathText(buf, true, s); - } else if (t == Tokens.END) { - // ignore this; when parsing a file, it should not happen - // since we're parsing a token list rather than the main - // token iterator, and when parsing a path expression from the - // API, it's expected to have an END. - } else { - // any periods outside of a quoted string count as - // separators - String text; - if (Tokens.isValue(t)) { - // appending a number here may add - // a period, but we _do_ count those as path - // separators, because we basically want - // "foo 3.0bar" to parse as a string even - // though there's a number in it. The fact that - // we tokenize non-string values is largely an - // implementation detail. - com.gu.typesafe.config.impl.AbstractConfigValue v = Tokens.getValue(t); - - // We need to split the tokens on a . so that we can get sub-paths but still preserve - // the original path text when doing an insertion - if (pathTokens != null) { - pathTokens.remove(pathTokens.size() - 1); - pathTokens.addAll(splitTokenOnPeriod(t, flavor)); - } - text = v.transformToString(); - } else if (Tokens.isUnquotedText(t)) { - // We need to split the tokens on a . so that we can get sub-paths but still preserve - // the original path text when doing an insertion on ConfigNodeObjects - if (pathTokens != null) { - pathTokens.remove(pathTokens.size() - 1); - pathTokens.addAll(splitTokenOnPeriod(t, flavor)); - } - text = Tokens.getUnquotedText(t); - } else { - throw new com.gu.typesafe.config.ConfigException.BadPath( - origin, - originalText, - "Token not allowed in path expression: " - + t - + " (you can double-quote this token if you really want it here)"); - } - - addPathText(buf, false, text); - } - } - - com.gu.typesafe.config.impl.PathBuilder pb = new com.gu.typesafe.config.impl.PathBuilder(); - for (Element e : buf) { - if (e.sb.length() == 0 && !e.canBeEmpty) { - throw new ConfigException.BadPath( - origin, - originalText, - "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)"); - } else { - pb.appendKey(e.sb.toString()); - } - } - - return pb.result(); - } - - private static Collection splitTokenOnPeriod(Token t, com.gu.typesafe.config.ConfigSyntax flavor) { - String tokenText = t.tokenText(); - if (tokenText.equals(".")) { - return Collections.singletonList(t); - } - String[] splitToken = tokenText.split("\\."); - ArrayList splitTokens = new ArrayList(); - for (String s : splitToken) { - if (flavor == ConfigSyntax.CONF) - splitTokens.add(Tokens.newUnquotedText(t.origin(), s)); - else - splitTokens.add(Tokens.newString(t.origin(), s, "\"" + s + "\"")); - splitTokens.add(Tokens.newUnquotedText(t.origin(), ".")); - } - if (tokenText.charAt(tokenText.length() - 1) != '.') - splitTokens.remove(splitTokens.size() - 1); - return splitTokens; - } - - private static void addPathText(List buf, boolean wasQuoted, - String newText) { - int i = wasQuoted ? -1 : newText.indexOf('.'); - Element current = buf.get(buf.size() - 1); - if (i < 0) { - // add to current path element - current.sb.append(newText); - // any empty quoted string means this element can - // now be empty. - if (wasQuoted && current.sb.length() == 0) - current.canBeEmpty = true; - } else { - // "buf" plus up to the period is an element - current.sb.append(newText.substring(0, i)); - // then start a new element - buf.add(new Element("", false)); - // recurse to consume remainder of newText - addPathText(buf, false, newText.substring(i + 1)); - } - } - - // the idea is to see if the string has any chars or features - // that might require the full parser to deal with. - private static boolean looksUnsafeForFastParser(String s) { - boolean lastWasDot = true; // start of path is also a "dot" - int len = s.length(); - if (s.isEmpty()) - return true; - if (s.charAt(0) == '.') - return true; - if (s.charAt(len - 1) == '.') - return true; - - for (int i = 0; i < len; ++i) { - char c = s.charAt(i); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { - lastWasDot = false; - continue; - } else if (c == '.') { - if (lastWasDot) - return true; // ".." means we need to throw an error - lastWasDot = true; - } else if (c == '-') { - if (lastWasDot) - return true; - continue; - } else { - return true; - } - } - - if (lastWasDot) - return true; - - return false; - } - - private static Path fastPathBuild(Path tail, String s, int end) { - // lastIndexOf takes last index it should look at, end - 1 not end - int splitAt = s.lastIndexOf('.', end - 1); - ArrayList tokens = new ArrayList(); - tokens.add(Tokens.newUnquotedText(null, s)); - // this works even if splitAt is -1; then we start the substring at 0 - Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail); - if (splitAt < 0) { - return withOneMoreElement; - } else { - return fastPathBuild(withOneMoreElement, s, splitAt); - } - } - - // do something much faster than the full parser if - // we just have something like "foo" or "foo.bar" - private static Path speculativeFastParsePath(String path) { - String s = ConfigImplUtil.unicodeTrim(path); - if (looksUnsafeForFastParser(s)) - return null; - - return fastPathBuild(null, s, s.length()); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/PropertiesParser.java b/scripts/src/main/java/com/gu/typesafe/config/impl/PropertiesParser.java deleted file mode 100644 index c4cd21606f3..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/PropertiesParser.java +++ /dev/null @@ -1,210 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; - -final class PropertiesParser { - static AbstractConfigObject parse(Reader reader, - com.gu.typesafe.config.ConfigOrigin origin) throws IOException { - Properties props = new Properties(); - props.load(reader); - return fromProperties(origin, props); - } - - static String lastElement(String path) { - int i = path.lastIndexOf('.'); - if (i < 0) - return path; - else - return path.substring(i + 1); - } - - static String exceptLastElement(String path) { - int i = path.lastIndexOf('.'); - if (i < 0) - return null; - else - return path.substring(0, i); - } - - static Path pathFromPropertyKey(String key) { - String last = lastElement(key); - String exceptLast = exceptLastElement(key); - Path path = new Path(last, null); - while (exceptLast != null) { - last = lastElement(exceptLast); - exceptLast = exceptLastElement(exceptLast); - path = new Path(last, path); - } - return path; - } - - static AbstractConfigObject fromProperties(com.gu.typesafe.config.ConfigOrigin origin, - Properties props) { - return fromEntrySet(origin, props.entrySet()); - } - - private static AbstractConfigObject fromEntrySet(com.gu.typesafe.config.ConfigOrigin origin, Set> entries) { - final Map pathMap = getPathMap(entries); - return fromPathMap(origin, pathMap, true /* from properties */); - } - - private static Map getPathMap(Set> entries) { - Map pathMap = new HashMap(); - for (Map.Entry entry : entries) { - Object key = entry.getKey(); - if (key instanceof String) { - Path path = pathFromPropertyKey((String) key); - pathMap.put(path, entry.getValue()); - } - } - return pathMap; - } - - static AbstractConfigObject fromStringMap(com.gu.typesafe.config.ConfigOrigin origin, Map stringMap) { - return fromEntrySet(origin, stringMap.entrySet()); - } - - static AbstractConfigObject fromPathMap(com.gu.typesafe.config.ConfigOrigin origin, - Map pathExpressionMap) { - Map pathMap = new HashMap(); - for (Map.Entry entry : pathExpressionMap.entrySet()) { - Object keyObj = entry.getKey(); - if (!(keyObj instanceof String)) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "Map has a non-string as a key, expecting a path expression as a String"); - } - Path path = Path.newPath((String) keyObj); - pathMap.put(path, entry.getValue()); - } - return fromPathMap(origin, pathMap, false /* from properties */); - } - - private static AbstractConfigObject fromPathMap(ConfigOrigin origin, - Map pathMap, boolean convertedFromProperties) { - /* - * First, build a list of paths that will have values, either string or - * object values. - */ - Set scopePaths = new HashSet(); - Set valuePaths = new HashSet(); - for (Path path : pathMap.keySet()) { - // add value's path - valuePaths.add(path); - - // all parent paths are objects - Path next = path.parent(); - while (next != null) { - scopePaths.add(next); - next = next.parent(); - } - } - - if (convertedFromProperties) { - /* - * If any string values are also objects containing other values, - * drop those string values - objects "win". - */ - valuePaths.removeAll(scopePaths); - } else { - /* If we didn't start out as properties, then this is an error. */ - for (Path path : valuePaths) { - if (scopePaths.contains(path)) { - throw new ConfigException.BugOrBroken( - "In the map, path '" - + path.render() - + "' occurs as both the parent object of a value and as a value. " - + "Because Map has no defined ordering, this is a broken situation."); - } - } - } - - /* - * Create maps for the object-valued values. - */ - Map root = new HashMap(); - Map> scopes = new HashMap>(); - - for (Path path : scopePaths) { - Map scope = new HashMap(); - scopes.put(path, scope); - } - - /* Store string values in the associated scope maps */ - for (Path path : valuePaths) { - Path parentPath = path.parent(); - Map parent = parentPath != null ? scopes - .get(parentPath) : root; - - String last = path.last(); - Object rawValue = pathMap.get(path); - com.gu.typesafe.config.impl.AbstractConfigValue value; - if (convertedFromProperties) { - if (rawValue instanceof String) { - value = new com.gu.typesafe.config.impl.ConfigString.Quoted(origin, (String) rawValue); - } else { - // silently ignore non-string values in Properties - value = null; - } - } else { - value = ConfigImpl.fromAnyRef(pathMap.get(path), origin, - FromMapMode.KEYS_ARE_PATHS); - } - if (value != null) - parent.put(last, value); - } - - /* - * Make a list of scope paths from longest to shortest, so children go - * before parents. - */ - List sortedScopePaths = new ArrayList(); - sortedScopePaths.addAll(scopePaths); - // sort descending by length - Collections.sort(sortedScopePaths, new Comparator() { - @Override - public int compare(Path a, Path b) { - // Path.length() is O(n) so in theory this sucks - // but in practice we can make Path precompute length - // if it ever matters. - return b.length() - a.length(); - } - }); - - /* - * Create ConfigObject for each scope map, working from children to - * parents to avoid modifying any already-created ConfigObject. This is - * where we need the sorted list. - */ - for (Path scopePath : sortedScopePaths) { - Map scope = scopes.get(scopePath); - - Path parentPath = scopePath.parent(); - Map parent = parentPath != null ? scopes - .get(parentPath) : root; - - AbstractConfigObject o = new com.gu.typesafe.config.impl.SimpleConfigObject(origin, scope, - ResolveStatus.RESOLVED, false /* ignoresFallbacks */); - parent.put(scopePath.last(), o); - } - - // return root config object - return new com.gu.typesafe.config.impl.SimpleConfigObject(origin, root, ResolveStatus.RESOLVED, - false /* ignoresFallbacks */); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ReplaceableMergeStack.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ReplaceableMergeStack.java deleted file mode 100644 index 0d532982fff..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ReplaceableMergeStack.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.gu.typesafe.config.impl; - -/** - * Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject) - * that replaces itself during substitution resolution in order to implement - * "look backwards only" semantics. - */ -interface ReplaceableMergeStack extends Container { - /** - * Make a replacement for this object skipping the given number of elements - * which are lower in merge priority. - */ - com.gu.typesafe.config.impl.AbstractConfigValue makeReplacement(com.gu.typesafe.config.impl.ResolveContext context, int skipping); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveContext.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveContext.java deleted file mode 100644 index 764d64afcca..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveContext.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.gu.typesafe.config.impl; - -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.ArrayList; -import java.util.Set; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigResolveOptions; -import com.gu.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; - -final class ResolveContext { - final private ResolveMemos memos; - - final private ConfigResolveOptions options; - // the current path restriction, used to ensure lazy - // resolution and avoid gratuitous cycles. without this, - // any sibling of an object we're traversing could - // cause a cycle "by side effect" - // CAN BE NULL for a full resolve. - final private Path restrictToChild; - - // This is used for tracing and debugging and nice error messages; - // contains every node as we call resolve on it. - final private List resolveStack; - - final private Set cycleMarkers; - - ResolveContext(ResolveMemos memos, ConfigResolveOptions options, Path restrictToChild, - List resolveStack, Set cycleMarkers) { - this.memos = memos; - this.options = options; - this.restrictToChild = restrictToChild; - // we don't defensively copy/wrap these because Collections.unmodifiableList etc. - // nest infinitely in a way that causes stack overflow (they don't check to avoid - // multiple wrappers). But they should be treated as immutable because they end - // up shared between multiple ResolveContext. - this.resolveStack = resolveStack; - this.cycleMarkers = cycleMarkers; - } - - private static Set newCycleMarkers() { - return Collections.newSetFromMap(new IdentityHashMap()); - } - - ResolveContext(ConfigResolveOptions options, Path restrictToChild) { - // LinkedHashSet keeps the traversal order which is at least useful - // in error messages if nothing else - this(new ResolveMemos(), options, restrictToChild, new ArrayList(), newCycleMarkers()); - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "ResolveContext restrict to child " + restrictToChild); - } - - ResolveContext addCycleMarker(com.gu.typesafe.config.impl.AbstractConfigValue value) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "++ Cycle marker " + value + "@" + System.identityHashCode(value)); - if (cycleMarkers.contains(value)) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Added cycle marker twice " + value); - Set copy = newCycleMarkers(); - copy.addAll(cycleMarkers); - copy.add(value); - return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); - } - - ResolveContext removeCycleMarker(com.gu.typesafe.config.impl.AbstractConfigValue value) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "-- Cycle marker " + value + "@" + System.identityHashCode(value)); - - Set copy = newCycleMarkers(); - copy.addAll(cycleMarkers); - copy.remove(value); - return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); - } - - private ResolveContext memoize(MemoKey key, com.gu.typesafe.config.impl.AbstractConfigValue value) { - ResolveMemos changed = memos.put(key, value); - return new ResolveContext(changed, options, restrictToChild, resolveStack, cycleMarkers); - } - - ConfigResolveOptions options() { - return options; - } - - boolean isRestrictedToChild() { - return restrictToChild != null; - } - - Path restrictToChild() { - return restrictToChild; - } - - // restrictTo may be null to unrestrict - ResolveContext restrict(Path restrictTo) { - if (restrictTo == restrictToChild) - return this; - else - return new ResolveContext(memos, options, restrictTo, resolveStack, cycleMarkers); - } - - ResolveContext unrestricted() { - return restrict(null); - } - - String traceString() { - String separator = ", "; - StringBuilder sb = new StringBuilder(); - for (com.gu.typesafe.config.impl.AbstractConfigValue value : resolveStack) { - if (value instanceof ConfigReference) { - sb.append(((ConfigReference) value).expression().toString()); - sb.append(separator); - } - } - if (sb.length() > 0) - sb.setLength(sb.length() - separator.length()); - return sb.toString(); - } - - private ResolveContext pushTrace(com.gu.typesafe.config.impl.AbstractConfigValue value) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "pushing trace " + value); - List copy = new ArrayList(resolveStack); - copy.add(value); - return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); - } - - ResolveContext popTrace() { - List copy = new ArrayList(resolveStack); - com.gu.typesafe.config.impl.AbstractConfigValue old = copy.remove(resolveStack.size() - 1); - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth() - 1, "popped trace " + old); - return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); - } - - int depth() { - if (resolveStack.size() > 30) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("resolve getting too deep"); - return resolveStack.size(); - } - - com.gu.typesafe.config.impl.ResolveResult resolve(com.gu.typesafe.config.impl.AbstractConfigValue original, ResolveSource source) - throws NotPossibleToResolve { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl - .trace(depth(), "resolving " + original + " restrictToChild=" + restrictToChild + " in " + source); - return pushTrace(original).realResolve(original, source).popTrace(); - } - - private com.gu.typesafe.config.impl.ResolveResult realResolve(com.gu.typesafe.config.impl.AbstractConfigValue original, ResolveSource source) - throws NotPossibleToResolve { - // a fully-resolved (no restrictToChild) object can satisfy a - // request for a restricted object, so always check that first. - final MemoKey fullKey = new MemoKey(original, null); - MemoKey restrictedKey = null; - - com.gu.typesafe.config.impl.AbstractConfigValue cached = memos.get(fullKey); - - // but if there was no fully-resolved object cached, we'll only - // compute the restrictToChild object so use a more limited - // memo key - if (cached == null && isRestrictedToChild()) { - restrictedKey = new MemoKey(original, restrictToChild()); - cached = memos.get(restrictedKey); - } - - if (cached != null) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "using cached resolution " + cached + " for " + original - + " restrictToChild " + restrictToChild()); - return com.gu.typesafe.config.impl.ResolveResult.make(this, cached); - } else { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), - "not found in cache, resolving " + original + "@" + System.identityHashCode(original)); - - if (cycleMarkers.contains(original)) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), - "Cycle detected, can't resolve; " + original + "@" + System.identityHashCode(original)); - throw new NotPossibleToResolve(this); - } - - com.gu.typesafe.config.impl.ResolveResult result = original.resolveSubstitutions(this, source); - com.gu.typesafe.config.impl.AbstractConfigValue resolved = result.value; - - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "resolved to " + resolved + "@" + System.identityHashCode(resolved) - + " from " + original + "@" + System.identityHashCode(resolved)); - - ResolveContext withMemo = result.context; - - if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) { - // if the resolved object is fully resolved by resolving - // only the restrictToChildOrNull, then it can be cached - // under fullKey since the child we were restricted to - // turned out to be the only unresolved thing. - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); - - withMemo = withMemo.memoize(fullKey, resolved); - } else { - // if we have an unresolved object then either we did a - // partial resolve restricted to a certain child, or we are - // allowing incomplete resolution, or it's a bug. - if (isRestrictedToChild()) { - if (restrictedKey == null) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "restrictedKey should not be null here"); - } - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "caching " + restrictedKey + " result " + resolved); - - withMemo = withMemo.memoize(restrictedKey, resolved); - } else if (options().getAllowUnresolved()) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); - - withMemo = withMemo.memoize(fullKey, resolved); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "resolveSubstitutions() did not give us a resolved object"); - } - } - - return com.gu.typesafe.config.impl.ResolveResult.make(withMemo, resolved); - } - } - - static com.gu.typesafe.config.impl.AbstractConfigValue resolve(com.gu.typesafe.config.impl.AbstractConfigValue value, AbstractConfigObject root, - ConfigResolveOptions options) { - ResolveSource source = new ResolveSource(root); - ResolveContext context = new ResolveContext(options, null /* restrictToChild */); - - try { - return context.resolve(value, source).value; - } catch (NotPossibleToResolve e) { - // ConfigReference was supposed to catch NotPossibleToResolve - throw new ConfigException.BugOrBroken( - "NotPossibleToResolve was thrown from an outermost resolve", e); - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveMemos.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveMemos.java deleted file mode 100644 index bfe2bd994a3..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveMemos.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.gu.typesafe.config.impl; - -/** - * This exists because we have to memoize resolved substitutions as we go - * through the config tree; otherwise we could end up creating multiple copies - * of values or whole trees of values as we follow chains of substitutions. - */ -final class ResolveMemos { - // note that we can resolve things to undefined (represented as Java null, - // rather than ConfigNull) so this map can have null values. - final private BadMap memos; - - private ResolveMemos(BadMap memos) { - this.memos = memos; - } - - ResolveMemos() { - this(new BadMap<>()); - } - - com.gu.typesafe.config.impl.AbstractConfigValue get(MemoKey key) { - return memos.get(key); - } - - ResolveMemos put(MemoKey key, com.gu.typesafe.config.impl.AbstractConfigValue value) { - return new ResolveMemos(memos.copyingPut(key, value)); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveResult.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveResult.java deleted file mode 100644 index db28246b273..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveResult.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; - -// value is allowed to be null -final class ResolveResult { - public final ResolveContext context; - public final V value; - - private ResolveResult(ResolveContext context, V value) { - this.context = context; - this.value = value; - } - - static ResolveResult make(ResolveContext context, V value) { - return new ResolveResult(context, value); - } - - // better option? we don't have variance - @SuppressWarnings("unchecked") - ResolveResult asObjectResult() { - if (!(value instanceof AbstractConfigObject)) - throw new ConfigException.BugOrBroken("Expecting a resolve result to be an object, but it was " + value); - Object o = this; - return (ResolveResult) o; - } - - // better option? we don't have variance - @SuppressWarnings("unchecked") - ResolveResult asValueResult() { - Object o = this; - return (ResolveResult) o; - } - - ResolveResult popTrace() { - return make(context.popTrace(), value); - } - - @Override - public String toString() { - return "ResolveResult(" + value + ")"; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveSource.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveSource.java deleted file mode 100644 index d18d088c635..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveSource.java +++ /dev/null @@ -1,350 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; - -/** - * This class is the source for values for a substitution like ${foo}. - */ -final class ResolveSource { - - final AbstractConfigObject root; - // This is used for knowing the chain of parents we used to get here. - // null if we should assume we are not a descendant of the root. - // the root itself should be a node in this if non-null. - final Node pathFromRoot; - - ResolveSource(AbstractConfigObject root, Node pathFromRoot) { - this.root = root; - this.pathFromRoot = pathFromRoot; - } - - ResolveSource(AbstractConfigObject root) { - this.root = root; - this.pathFromRoot = null; - } - - // if we replace the root with a non-object, use an empty - // object with nothing in it instead. - private AbstractConfigObject rootMustBeObj(Container value) { - if (value instanceof AbstractConfigObject) { - return (AbstractConfigObject) value; - } else { - return SimpleConfigObject.empty(); - } - } - - // as a side effect, findInObject() will have to resolve all parents of the - // child being peeked, but NOT the child itself. Caller has to resolve - // the child itself if needed. ValueWithPath.value can be null but - // the ValueWithPath instance itself should not be. - static private ResultWithPath findInObject(AbstractConfigObject obj, com.gu.typesafe.config.impl.ResolveContext context, Path path) - throws NotPossibleToResolve { - // resolve ONLY portions of the object which are along our path - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace("*** finding '" + path + "' in " + obj); - Path restriction = context.restrictToChild(); - com.gu.typesafe.config.impl.ResolveResult partiallyResolved = context.restrict(path).resolve(obj, - new ResolveSource(obj)); - com.gu.typesafe.config.impl.ResolveContext newContext = partiallyResolved.context.restrict(restriction); - if (partiallyResolved.value instanceof AbstractConfigObject) { - ValueWithPath pair = findInObject((AbstractConfigObject) partiallyResolved.value, path); - return new ResultWithPath(com.gu.typesafe.config.impl.ResolveResult.make(newContext, pair.value), pair.pathFromRoot); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("resolved object to non-object " + obj + " to " + partiallyResolved); - } - } - - static private ValueWithPath findInObject(AbstractConfigObject obj, Path path) { - try { - // we'll fail if anything along the path can't - // be looked at without resolving. - return findInObject(obj, path, null); - } catch (com.gu.typesafe.config.ConfigException.NotResolved e) { - throw ConfigImpl.improveNotResolved(path, e); - } - } - - static private ValueWithPath findInObject(AbstractConfigObject obj, Path path, Node parents) { - String key = path.first(); - Path next = path.remainder(); - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace("*** looking up '" + key + "' in " + obj); - com.gu.typesafe.config.impl.AbstractConfigValue v = obj.attemptPeekWithPartialResolve(key); - Node newParents = parents == null ? new Node(obj) : parents.prepend(obj); - - if (next == null) { - return new ValueWithPath(v, newParents); - } else { - if (v instanceof AbstractConfigObject) { - return findInObject((AbstractConfigObject) v, next, newParents); - } else { - return new ValueWithPath(null, newParents); - } - } - } - - ResultWithPath lookupSubst(com.gu.typesafe.config.impl.ResolveContext context, SubstitutionExpression subst, - int prefixLength) - throws NotPossibleToResolve { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), "searching for " + subst); - - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(context.depth(), subst + " - looking up relative to file it occurred in"); - // First we look up the full path, which means relative to the - // included file if we were not a root file - ResultWithPath result = findInObject(root, context, subst.path()); - - if (result.result.value == null) { - // Then we want to check relative to the root file. We don't - // want the prefix we were included at to be used when looking - // up env variables either. - Path unprefixed = subst.path().subPath(prefixLength); - - if (prefixLength > 0) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(result.result.context.depth(), unprefixed - + " - looking up relative to parent file"); - result = findInObject(root, result.result.context, unprefixed); - } - - if (result.result.value == null && result.result.context.options().getUseSystemEnvironment()) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up in system environment"); - result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed); - } - } - - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace(result.result.context.depth(), "resolved to " + result); - - return result; - } - - ResolveSource pushParent(Container parent) { - if (parent == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("can't push null parent"); - - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace("pushing parent " + parent + " ==root " + (parent == root) + " onto " + this); - - if (pathFromRoot == null) { - if (parent == root) { - return new ResolveSource(root, new Node(parent)); - } else { - if (ConfigImpl.traceSubstitutionsEnabled()) { - // this hasDescendant check is super-expensive so it's a - // trace message rather than an assertion - if (root.hasDescendant((com.gu.typesafe.config.impl.AbstractConfigValue) parent)) - ConfigImpl.trace("***** BUG ***** tried to push parent " + parent - + " without having a path to it in " + this); - } - // ignore parents if we aren't proceeding from the - // root - return this; - } - } else { - Container parentParent = pathFromRoot.head(); - if (ConfigImpl.traceSubstitutionsEnabled()) { - // this hasDescendant check is super-expensive so it's a - // trace message rather than an assertion - if (parentParent != null && !parentParent.hasDescendant((com.gu.typesafe.config.impl.AbstractConfigValue) parent)) - ConfigImpl.trace("***** BUG ***** trying to push non-child of " + parentParent + ", non-child was " - + parent); - } - - return new ResolveSource(root, pathFromRoot.prepend(parent)); - } - } - - ResolveSource resetParents() { - if (pathFromRoot == null) - return this; - else - return new ResolveSource(root); - } - - // returns null if the replacement results in deleting all the nodes. - private static Node replace(Node list, Container old, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - Container child = list.head(); - if (child != old) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Can only replace() the top node we're resolving; had " + child - + " on top and tried to replace " + old + " overall list was " + list); - Container parent = list.tail() == null ? null : list.tail().head(); - if (replacement == null || !(replacement instanceof Container)) { - if (parent == null) { - return null; - } else { - /* - * we are deleting the child from the stack of containers - * because it's either going away or not a container - */ - com.gu.typesafe.config.impl.AbstractConfigValue newParent = parent.replaceChild((com.gu.typesafe.config.impl.AbstractConfigValue) old, null); - - return replace(list.tail(), parent, newParent); - } - } else { - /* we replaced the container with another container */ - if (parent == null) { - return new Node((Container) replacement); - } else { - com.gu.typesafe.config.impl.AbstractConfigValue newParent = parent.replaceChild((com.gu.typesafe.config.impl.AbstractConfigValue) old, replacement); - Node newTail = replace(list.tail(), parent, newParent); - if (newTail != null) - return newTail.prepend((Container) replacement); - else - return new Node((Container) replacement); - } - } - } - - ResolveSource replaceCurrentParent(Container old, Container replacement) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace("replaceCurrentParent old " + old + "@" + System.identityHashCode(old) + " replacement " - + replacement + "@" + System.identityHashCode(old) + " in " + this); - if (old == replacement) { - return this; - } else if (pathFromRoot != null) { - Node newPath = replace(pathFromRoot, old, (com.gu.typesafe.config.impl.AbstractConfigValue) replacement); - if (ConfigImpl.traceSubstitutionsEnabled()) { - ConfigImpl.trace("replaced " + old + " with " + replacement + " in " + this); - ConfigImpl.trace("path was: " + pathFromRoot + " is now " + newPath); - } - // if we end up nuking the root object itself, we replace it with an - // empty root - if (newPath != null) - return new ResolveSource((AbstractConfigObject) newPath.last(), newPath); - else - return new ResolveSource(SimpleConfigObject.empty()); - } else { - if (old == root) { - return new ResolveSource(rootMustBeObj(replacement)); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("attempt to replace root " + root + " with " + replacement); - // return this; - } - } - } - - // replacement may be null to delete - ResolveSource replaceWithinCurrentParent(com.gu.typesafe.config.impl.AbstractConfigValue old, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - if (ConfigImpl.traceSubstitutionsEnabled()) - ConfigImpl.trace("replaceWithinCurrentParent old " + old + "@" + System.identityHashCode(old) - + " replacement " + replacement + "@" + System.identityHashCode(old) + " in " + this); - if (old == replacement) { - return this; - } else if (pathFromRoot != null) { - Container parent = pathFromRoot.head(); - com.gu.typesafe.config.impl.AbstractConfigValue newParent = parent.replaceChild(old, replacement); - return replaceCurrentParent(parent, (newParent instanceof Container) ? (Container) newParent : null); - } else { - if (old == root && replacement instanceof Container) { - return new ResolveSource(rootMustBeObj((Container) replacement)); - } else { - throw new ConfigException.BugOrBroken("replace in parent not possible " + old + " with " + replacement - + " in " + this); - // return this; - } - } - } - - @Override - public String toString() { - return "ResolveSource(root=" + root + ", pathFromRoot=" + pathFromRoot + ")"; - } - - // a persistent list - static final class Node { - final T value; - final Node next; - - Node(T value, Node next) { - this.value = value; - this.next = next; - } - - Node(T value) { - this(value, null); - } - - Node prepend(T value) { - return new Node(value, this); - } - - T head() { - return value; - } - - Node tail() { - return next; - } - - T last() { - Node i = this; - while (i.next != null) - i = i.next; - return i.value; - } - - Node reverse() { - if (next == null) { - return this; - } else { - Node reversed = new Node(value); - Node i = next; - while (i != null) { - reversed = reversed.prepend(i.value); - i = i.next; - } - return reversed; - } - } - - @Override - public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("["); - Node toAppendValue = this.reverse(); - while (toAppendValue != null) { - sb.append(toAppendValue.value.toString()); - if (toAppendValue.next != null) - sb.append(" <= "); - toAppendValue = toAppendValue.next; - } - sb.append("]"); - return sb.toString(); - } - } - - // value is allowed to be null - static final class ValueWithPath { - final com.gu.typesafe.config.impl.AbstractConfigValue value; - final Node pathFromRoot; - - ValueWithPath(com.gu.typesafe.config.impl.AbstractConfigValue value, Node pathFromRoot) { - this.value = value; - this.pathFromRoot = pathFromRoot; - } - - @Override - public String toString() { - return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")"; - } - } - - static final class ResultWithPath { - final com.gu.typesafe.config.impl.ResolveResult result; - final Node pathFromRoot; - - ResultWithPath(com.gu.typesafe.config.impl.ResolveResult result, Node pathFromRoot) { - this.result = result; - this.pathFromRoot = pathFromRoot; - } - - @Override - public String toString() { - return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")"; - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveStatus.java b/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveStatus.java deleted file mode 100644 index 6c9c707e5d1..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/ResolveStatus.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.Collection; - -/** - * Status of substitution resolution. - */ -enum ResolveStatus { - UNRESOLVED, RESOLVED; - - final static ResolveStatus fromValues( - Collection values) { - for (com.gu.typesafe.config.impl.AbstractConfigValue v : values) { - if (v.resolveStatus() == ResolveStatus.UNRESOLVED) - return ResolveStatus.UNRESOLVED; - } - return ResolveStatus.RESOLVED; - } - - final static ResolveStatus fromBoolean(boolean resolved) { - return resolved ? ResolveStatus.RESOLVED : ResolveStatus.UNRESOLVED; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SerializedConfigValue.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SerializedConfigValue.java deleted file mode 100644 index 3a58150ae8f..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SerializedConfigValue.java +++ /dev/null @@ -1,528 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.Externalizable; -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.io.ObjectStreamException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.gu.typesafe.config.*; - -/** - * Deliberately shoving all the serialization code into this class instead of - * doing it OO-style with each subclass. Seems better to have it all in one - * place. This class implements a lame serialization format that supports - * skipping unknown fields, so it's moderately more extensible than the default - * Java serialization format. - */ -class SerializedConfigValue extends com.gu.typesafe.config.impl.AbstractConfigValue implements Externalizable { - - // this is the version used by Java serialization, if it increments it's - // essentially an ABI break and bad - private static final long serialVersionUID = 1L; - - // this is how we try to be extensible - static enum SerializedField { - // represents a field code we didn't recognize - UNKNOWN, - - // end of a list of fields - END_MARKER, - - // Fields at the root - ROOT_VALUE, - ROOT_WAS_CONFIG, - - // Fields that make up a value - VALUE_DATA, - VALUE_ORIGIN, - - // Fields that make up an origin - ORIGIN_DESCRIPTION, - ORIGIN_LINE_NUMBER, - ORIGIN_END_LINE_NUMBER, - ORIGIN_TYPE, - ORIGIN_URL, - ORIGIN_COMMENTS, - ORIGIN_NULL_URL, - ORIGIN_NULL_COMMENTS, - ORIGIN_RESOURCE, - ORIGIN_NULL_RESOURCE; - - static SerializedField forInt(int b) { - if (b < values().length) - return values()[b]; - else - return UNKNOWN; - } - }; - - private static enum SerializedValueType { - // the ordinals here are in the wire format, caution - NULL(com.gu.typesafe.config.ConfigValueType.NULL), - BOOLEAN(com.gu.typesafe.config.ConfigValueType.BOOLEAN), - INT(com.gu.typesafe.config.ConfigValueType.NUMBER), - LONG(com.gu.typesafe.config.ConfigValueType.NUMBER), - DOUBLE(com.gu.typesafe.config.ConfigValueType.NUMBER), - STRING(com.gu.typesafe.config.ConfigValueType.STRING), - LIST(com.gu.typesafe.config.ConfigValueType.LIST), - OBJECT(com.gu.typesafe.config.ConfigValueType.OBJECT); - - com.gu.typesafe.config.ConfigValueType configType; - - SerializedValueType(com.gu.typesafe.config.ConfigValueType configType) { - this.configType = configType; - } - - static SerializedValueType forInt(int b) { - if (b < values().length) - return values()[b]; - else - return null; - } - - static SerializedValueType forValue(com.gu.typesafe.config.ConfigValue value) { - com.gu.typesafe.config.ConfigValueType t = value.valueType(); - if (t == com.gu.typesafe.config.ConfigValueType.NUMBER) { - if (value instanceof com.gu.typesafe.config.impl.ConfigInt) - return INT; - else if (value instanceof ConfigLong) - return LONG; - else if (value instanceof ConfigDouble) - return DOUBLE; - } else { - for (SerializedValueType st : values()) { - if (st.configType == t) - return st; - } - } - - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("don't know how to serialize " + value); - } - }; - - private com.gu.typesafe.config.ConfigValue value; - private boolean wasConfig; - - // this has to be public for the Java deserializer - public SerializedConfigValue() { - super(null); - } - - SerializedConfigValue(com.gu.typesafe.config.ConfigValue value) { - this(); - this.value = value; - this.wasConfig = false; - } - - SerializedConfigValue(Config conf) { - this(conf.root()); - this.wasConfig = true; - } - - // when Java deserializer reads this object, return the contained - // object instead. - private Object readResolve() throws ObjectStreamException { - if (wasConfig) - return ((com.gu.typesafe.config.ConfigObject) value).toConfig(); - else - return value; - } - - private static class FieldOut { - final SerializedField code; - final ByteArrayOutputStream bytes; - final DataOutput data; - - FieldOut(SerializedField code) { - this.code = code; - this.bytes = new ByteArrayOutputStream(); - this.data = new DataOutputStream(bytes); - } - } - - // this is a separate function to prevent bugs writing to the - // outer stream instead of field.data - private static void writeOriginField(DataOutput out, SerializedField code, Object v) - throws IOException { - switch (code) { - case ORIGIN_DESCRIPTION: - out.writeUTF((String) v); - break; - case ORIGIN_LINE_NUMBER: - out.writeInt((Integer) v); - break; - case ORIGIN_END_LINE_NUMBER: - out.writeInt((Integer) v); - break; - case ORIGIN_TYPE: - out.writeByte((Integer) v); - break; - case ORIGIN_URL: - out.writeUTF((String) v); - break; - case ORIGIN_RESOURCE: - out.writeUTF((String) v); - break; - case ORIGIN_COMMENTS: - @SuppressWarnings("unchecked") - List list = (List) v; - int size = list.size(); - out.writeInt(size); - for (String s : list) { - out.writeUTF(s); - } - break; - case ORIGIN_NULL_URL: // FALL THRU - case ORIGIN_NULL_RESOURCE: // FALL THRU - case ORIGIN_NULL_COMMENTS: - // nothing to write out besides code and length - break; - default: - throw new IOException("Unhandled field from origin: " + code); - } - } - - // not private because we use it to serialize ConfigException - static void writeOrigin(DataOutput out, SimpleConfigOrigin origin, - SimpleConfigOrigin baseOrigin) throws IOException { - Map m; - // to serialize a null origin, we write out no fields at all - if (origin != null) - m = origin.toFieldsDelta(baseOrigin); - else - m = Collections.emptyMap(); - for (Map.Entry e : m.entrySet()) { - FieldOut field = new FieldOut(e.getKey()); - Object v = e.getValue(); - writeOriginField(field.data, field.code, v); - writeField(out, field); - } - writeEndMarker(out); - } - - // not private because we use it to deserialize ConfigException - static SimpleConfigOrigin readOrigin(DataInput in, SimpleConfigOrigin baseOrigin) - throws IOException { - Map m = new EnumMap(SerializedField.class); - while (true) { - Object v = null; - SerializedField field = readCode(in); - switch (field) { - case END_MARKER: - return SimpleConfigOrigin.fromBase(baseOrigin, m); - case ORIGIN_DESCRIPTION: - in.readInt(); // discard length - v = in.readUTF(); - break; - case ORIGIN_LINE_NUMBER: - in.readInt(); // discard length - v = in.readInt(); - break; - case ORIGIN_END_LINE_NUMBER: - in.readInt(); // discard length - v = in.readInt(); - break; - case ORIGIN_TYPE: - in.readInt(); // discard length - v = in.readUnsignedByte(); - break; - case ORIGIN_URL: - in.readInt(); // discard length - v = in.readUTF(); - break; - case ORIGIN_RESOURCE: - in.readInt(); // discard length - v = in.readUTF(); - break; - case ORIGIN_COMMENTS: - in.readInt(); // discard length - int size = in.readInt(); - List list = new ArrayList(size); - for (int i = 0; i < size; ++i) { - list.add(in.readUTF()); - } - v = list; - break; - case ORIGIN_NULL_URL: // FALL THRU - case ORIGIN_NULL_RESOURCE: // FALL THRU - case ORIGIN_NULL_COMMENTS: - // nothing to read besides code and length - in.readInt(); // discard length - v = ""; // just something non-null to put in the map - break; - case ROOT_VALUE: - case ROOT_WAS_CONFIG: - case VALUE_DATA: - case VALUE_ORIGIN: - throw new IOException("Not expecting this field here: " + field); - case UNKNOWN: - // skip unknown field - skipField(in); - break; - } - if (v != null) - m.put(field, v); - } - } - - private static void writeValueData(DataOutput out, com.gu.typesafe.config.ConfigValue value) throws IOException { - SerializedValueType st = SerializedValueType.forValue(value); - out.writeByte(st.ordinal()); - switch (st) { - case BOOLEAN: - out.writeBoolean(((ConfigBoolean) value).unwrapped()); - break; - case NULL: - break; - case INT: - // saving numbers as both string and binary is redundant but easy - out.writeInt(((com.gu.typesafe.config.impl.ConfigInt) value).unwrapped()); - out.writeUTF(((ConfigNumber) value).transformToString()); - break; - case LONG: - out.writeLong(((ConfigLong) value).unwrapped()); - out.writeUTF(((ConfigNumber) value).transformToString()); - break; - case DOUBLE: - out.writeDouble(((ConfigDouble) value).unwrapped()); - out.writeUTF(((ConfigNumber) value).transformToString()); - break; - case STRING: - out.writeUTF(((ConfigString) value).unwrapped()); - break; - case LIST: - com.gu.typesafe.config.ConfigList list = (ConfigList) value; - out.writeInt(list.size()); - for (com.gu.typesafe.config.ConfigValue v : list) { - writeValue(out, v, (SimpleConfigOrigin) list.origin()); - } - break; - case OBJECT: - com.gu.typesafe.config.ConfigObject obj = (ConfigObject) value; - out.writeInt(obj.size()); - for (Map.Entry e : obj.entrySet()) { - out.writeUTF(e.getKey()); - writeValue(out, e.getValue(), (SimpleConfigOrigin) obj.origin()); - } - break; - } - } - - private static com.gu.typesafe.config.impl.AbstractConfigValue readValueData(DataInput in, SimpleConfigOrigin origin) - throws IOException { - int stb = in.readUnsignedByte(); - SerializedValueType st = SerializedValueType.forInt(stb); - if (st == null) - throw new IOException("Unknown serialized value type: " + stb); - switch (st) { - case BOOLEAN: - return new ConfigBoolean(origin, in.readBoolean()); - case NULL: - return new ConfigNull(origin); - case INT: - int vi = in.readInt(); - String si = in.readUTF(); - return new com.gu.typesafe.config.impl.ConfigInt(origin, vi, si); - case LONG: - long vl = in.readLong(); - String sl = in.readUTF(); - return new ConfigLong(origin, vl, sl); - case DOUBLE: - double vd = in.readDouble(); - String sd = in.readUTF(); - return new ConfigDouble(origin, vd, sd); - case STRING: - return new ConfigString.Quoted(origin, in.readUTF()); - case LIST: - int listSize = in.readInt(); - List list = new ArrayList(listSize); - for (int i = 0; i < listSize; ++i) { - com.gu.typesafe.config.impl.AbstractConfigValue v = readValue(in, origin); - list.add(v); - } - return new SimpleConfigList(origin, list); - case OBJECT: - int mapSize = in.readInt(); - Map map = new HashMap(mapSize); - for (int i = 0; i < mapSize; ++i) { - String key = in.readUTF(); - com.gu.typesafe.config.impl.AbstractConfigValue v = readValue(in, origin); - map.put(key, v); - } - return new SimpleConfigObject(origin, map); - } - throw new IOException("Unhandled serialized value type: " + st); - } - - private static void writeValue(DataOutput out, ConfigValue value, SimpleConfigOrigin baseOrigin) - throws IOException { - FieldOut origin = new FieldOut(SerializedField.VALUE_ORIGIN); - writeOrigin(origin.data, (SimpleConfigOrigin) value.origin(), - baseOrigin); - writeField(out, origin); - - FieldOut data = new FieldOut(SerializedField.VALUE_DATA); - writeValueData(data.data, value); - writeField(out, data); - - writeEndMarker(out); - } - - private static com.gu.typesafe.config.impl.AbstractConfigValue readValue(DataInput in, SimpleConfigOrigin baseOrigin) - throws IOException { - com.gu.typesafe.config.impl.AbstractConfigValue value = null; - SimpleConfigOrigin origin = null; - while (true) { - SerializedField code = readCode(in); - if (code == SerializedField.END_MARKER) { - if (value == null) - throw new IOException("No value data found in serialization of value"); - return value; - } else if (code == SerializedField.VALUE_DATA) { - if (origin == null) - throw new IOException("Origin must be stored before value data"); - in.readInt(); // discard length - value = readValueData(in, origin); - } else if (code == SerializedField.VALUE_ORIGIN) { - in.readInt(); // discard length - origin = readOrigin(in, baseOrigin); - } else { - // ignore unknown field - skipField(in); - } - } - } - - private static void writeField(DataOutput out, FieldOut field) throws IOException { - byte[] bytes = field.bytes.toByteArray(); - out.writeByte(field.code.ordinal()); - out.writeInt(bytes.length); - out.write(bytes); - } - - private static void writeEndMarker(DataOutput out) throws IOException { - out.writeByte(SerializedField.END_MARKER.ordinal()); - } - - private static SerializedField readCode(DataInput in) throws IOException { - int c = in.readUnsignedByte(); - if (c == SerializedField.UNKNOWN.ordinal()) - throw new IOException("field code " + c + " is not supposed to be on the wire"); - return SerializedField.forInt(c); - } - - private static void skipField(DataInput in) throws IOException { - int len = in.readInt(); - // skipBytes doesn't have to block - int skipped = in.skipBytes(len); - if (skipped < len) { - // wastefully use readFully() if skipBytes didn't work - byte[] bytes = new byte[(len - skipped)]; - in.readFully(bytes); - } - } - - @Override - public void writeExternal(ObjectOutput out) throws IOException { - if (((com.gu.typesafe.config.impl.AbstractConfigValue) value).resolveStatus() != ResolveStatus.RESOLVED) - throw new NotSerializableException( - "tried to serialize a value with unresolved substitutions, need to Config#resolve() first, see API docs"); - FieldOut field = new FieldOut(SerializedField.ROOT_VALUE); - writeValue(field.data, value, null /* baseOrigin */); - writeField(out, field); - - field = new FieldOut(SerializedField.ROOT_WAS_CONFIG); - field.data.writeBoolean(wasConfig); - writeField(out, field); - - writeEndMarker(out); - } - - @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - while (true) { - SerializedField code = readCode(in); - if (code == SerializedField.END_MARKER) { - return; - } - - DataInput input = fieldIn(in); - if (code == SerializedField.ROOT_VALUE) { - this.value = readValue(input, null /* baseOrigin */); - } else if (code == SerializedField.ROOT_WAS_CONFIG) { - this.wasConfig = input.readBoolean(); - } - } - } - - private DataInput fieldIn(ObjectInput in) throws IOException { - byte[] bytes = new byte[in.readInt()]; - in.readFully(bytes); - return new DataInputStream(new ByteArrayInputStream(bytes)); - } - - private static com.gu.typesafe.config.ConfigException shouldNotBeUsed() { - return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName() - + " should not exist outside of serialization"); - } - - @Override - public ConfigValueType valueType() { - throw shouldNotBeUsed(); - } - - @Override - public Object unwrapped() { - throw shouldNotBeUsed(); - } - - @Override - protected SerializedConfigValue newCopy(ConfigOrigin origin) { - throw shouldNotBeUsed(); - } - - @Override - public final String toString() { - return getClass().getSimpleName() + "(value=" + value + ",wasConfig=" + wasConfig + ")"; - } - - @Override - public boolean equals(Object other) { - // there's no reason we will ever call this equals(), but - // the one in AbstractConfigValue would explode due to - // calling unwrapped() above, so we just give some - // safe-to-call implementation to avoid breaking the - // contract of java.lang.Object - if (other instanceof SerializedConfigValue) { - return canEqual(other) - && (this.wasConfig == ((SerializedConfigValue) other).wasConfig) - && (this.value.equals(((SerializedConfigValue) other).value)); - } else { - return false; - } - } - - @Override - public int hashCode() { - int h = 41 * (41 + value.hashCode()); - h = 41 * (h + (wasConfig ? 1 : 0)); - return h; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfig.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfig.java deleted file mode 100644 index dc9562b8bf9..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfig.java +++ /dev/null @@ -1,1185 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.DateTimeException; -import java.time.Duration; -import java.time.Period; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalAmount; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import com.gu.typesafe.config.*; - -/** - * One thing to keep in mind in the future: as Collection-like APIs are added - * here, including iterators or size() or anything, they should be consistent - * with a one-level java.util.Map from paths to non-null values. Null values are - * not "in" the map. - */ -final class SimpleConfig implements com.gu.typesafe.config.Config, MergeableValue, Serializable { - - private static final long serialVersionUID = 1L; - - final private AbstractConfigObject object; - - SimpleConfig(AbstractConfigObject object) { - this.object = object; - } - - @Override - public AbstractConfigObject root() { - return object; - } - - @Override - public com.gu.typesafe.config.ConfigOrigin origin() { - return object.origin(); - } - - @Override - public SimpleConfig resolve() { - return resolve(com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - @Override - public SimpleConfig resolve(com.gu.typesafe.config.ConfigResolveOptions options) { - return resolveWith(this, options); - } - - @Override - public SimpleConfig resolveWith(com.gu.typesafe.config.Config source) { - return resolveWith(source, com.gu.typesafe.config.ConfigResolveOptions.defaults()); - } - - @Override - public SimpleConfig resolveWith(com.gu.typesafe.config.Config source, ConfigResolveOptions options) { - com.gu.typesafe.config.impl.AbstractConfigValue resolved = ResolveContext.resolve(object, ((SimpleConfig) source).object, options); - - if (resolved == object) - return this; - else - return new SimpleConfig((AbstractConfigObject) resolved); - } - - private com.gu.typesafe.config.ConfigValue hasPathPeek(String pathExpression) { - Path path = Path.newPath(pathExpression); - com.gu.typesafe.config.ConfigValue peeked; - try { - peeked = object.peekPath(path); - } catch (com.gu.typesafe.config.ConfigException.NotResolved e) { - throw ConfigImpl.improveNotResolved(path, e); - } - return peeked; - } - - @Override - public boolean hasPath(String pathExpression) { - com.gu.typesafe.config.ConfigValue peeked = hasPathPeek(pathExpression); - return peeked != null && peeked.valueType() != com.gu.typesafe.config.ConfigValueType.NULL; - } - - @Override - public boolean hasPathOrNull(String path) { - com.gu.typesafe.config.ConfigValue peeked = hasPathPeek(path); - return peeked != null; - } - - @Override - public boolean isEmpty() { - return object.isEmpty(); - } - - private static void findPaths(Set> entries, Path parent, - AbstractConfigObject obj) { - for (Map.Entry entry : obj.entrySet()) { - String elem = entry.getKey(); - com.gu.typesafe.config.ConfigValue v = entry.getValue(); - Path path = Path.newKey(elem); - if (parent != null) - path = path.prepend(parent); - if (v instanceof AbstractConfigObject) { - findPaths(entries, path, (AbstractConfigObject) v); - } else if (v instanceof com.gu.typesafe.config.impl.ConfigNull) { - // nothing; nulls are conceptually not in a Config - } else { - entries.add(new AbstractMap.SimpleImmutableEntry(path.render(), v)); - } - } - } - - @Override - public Set> entrySet() { - Set> entries = new HashSet>(); - findPaths(entries, null, object); - return entries; - } - - static private com.gu.typesafe.config.impl.AbstractConfigValue throwIfNull(com.gu.typesafe.config.impl.AbstractConfigValue v, com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - if (v.valueType() == com.gu.typesafe.config.ConfigValueType.NULL) - throw new com.gu.typesafe.config.ConfigException.Null(v.origin(), originalPath.render(), - expected != null ? expected.name() : null); - else - return v; - } - - static private com.gu.typesafe.config.impl.AbstractConfigValue findKey(AbstractConfigObject self, String key, - com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - return throwIfNull(findKeyOrNull(self, key, expected, originalPath), expected, originalPath); - } - - static private com.gu.typesafe.config.impl.AbstractConfigValue findKeyOrNull(AbstractConfigObject self, String key, - com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - com.gu.typesafe.config.impl.AbstractConfigValue v = self.peekAssumingResolved(key, originalPath); - if (v == null) - throw new com.gu.typesafe.config.ConfigException.Missing(self.origin(), originalPath.render()); - - if (expected != null) - v = DefaultTransformer.transform(v, expected); - - if (expected != null && (v.valueType() != expected && v.valueType() != com.gu.typesafe.config.ConfigValueType.NULL)) - throw new com.gu.typesafe.config.ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(), - v.valueType().name()); - else - return v; - } - - static private com.gu.typesafe.config.impl.AbstractConfigValue findOrNull(AbstractConfigObject self, Path path, - com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - try { - String key = path.first(); - Path next = path.remainder(); - if (next == null) { - return findKeyOrNull(self, key, expected, originalPath); - } else { - AbstractConfigObject o = (AbstractConfigObject) findKey(self, key, - com.gu.typesafe.config.ConfigValueType.OBJECT, - originalPath.subPath(0, originalPath.length() - next.length())); - assert (o != null); // missing was supposed to throw - return findOrNull(o, next, expected, originalPath); - } - } catch (com.gu.typesafe.config.ConfigException.NotResolved e) { - throw ConfigImpl.improveNotResolved(path, e); - } - } - - com.gu.typesafe.config.impl.AbstractConfigValue find(Path pathExpression, com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - return throwIfNull(findOrNull(object, pathExpression, expected, originalPath), expected, originalPath); - } - - com.gu.typesafe.config.impl.AbstractConfigValue find(String pathExpression, com.gu.typesafe.config.ConfigValueType expected) { - Path path = Path.newPath(pathExpression); - return find(path, expected, path); - } - - private com.gu.typesafe.config.impl.AbstractConfigValue findOrNull(Path pathExpression, com.gu.typesafe.config.ConfigValueType expected, Path originalPath) { - return findOrNull(object, pathExpression, expected, originalPath); - } - - private com.gu.typesafe.config.impl.AbstractConfigValue findOrNull(String pathExpression, com.gu.typesafe.config.ConfigValueType expected) { - Path path = Path.newPath(pathExpression); - return findOrNull(path, expected, path); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue getValue(String path) { - return find(path, null); - } - - @Override - public boolean getIsNull(String path) { - com.gu.typesafe.config.impl.AbstractConfigValue v = findOrNull(path, null); - return (v.valueType() == com.gu.typesafe.config.ConfigValueType.NULL); - } - - @Override - public boolean getBoolean(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.BOOLEAN); - return (Boolean) v.unwrapped(); - } - - private com.gu.typesafe.config.impl.ConfigNumber getConfigNumber(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.NUMBER); - return (com.gu.typesafe.config.impl.ConfigNumber) v; - } - - @Override - public Number getNumber(String path) { - return getConfigNumber(path).unwrapped(); - } - - @Override - public int getInt(String path) { - com.gu.typesafe.config.impl.ConfigNumber n = getConfigNumber(path); - return n.intValueRangeChecked(path); - } - - @Override - public long getLong(String path) { - return getNumber(path).longValue(); - } - - @Override - public double getDouble(String path) { - return getNumber(path).doubleValue(); - } - - @Override - public String getString(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - return (String) v.unwrapped(); - } - - @Override - public > T getEnum(Class enumClass, String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - return getEnumValue(path, enumClass, v); - } - - @Override - public com.gu.typesafe.config.ConfigList getList(String path) { - com.gu.typesafe.config.impl.AbstractConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.LIST); - return (ConfigList) v; - } - - @Override - public AbstractConfigObject getObject(String path) { - AbstractConfigObject obj = (AbstractConfigObject) find(path, com.gu.typesafe.config.ConfigValueType.OBJECT); - return obj; - } - - @Override - public SimpleConfig getConfig(String path) { - return getObject(path).toConfig(); - } - - @Override - public Object getAnyRef(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, null); - return v.unwrapped(); - } - - @Override - public Long getBytes(String path) { - BigInteger bytes = getBytesBigInteger(path); - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - return toLong(bytes, v.origin(), path); - } - - private BigInteger getBytesBigInteger(String path) { - BigInteger bytes; - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - try { - bytes = BigInteger.valueOf(getLong(path)); - } catch (com.gu.typesafe.config.ConfigException.WrongType e) { - bytes = parseBytes((String) v.unwrapped(), - v.origin(), path); - } - if (bytes.signum() < 0) - throw new com.gu.typesafe.config.ConfigException.BadValue(v.origin(), path, - "Attempt to construct memory size with negative number: " + bytes); - return bytes; - } - - private List getBytesListBigInteger(String path){ - List result = new ArrayList<>(); - List list = getList(path); - - for (com.gu.typesafe.config.ConfigValue v : list) { - BigInteger bytes; - if (v.valueType() == com.gu.typesafe.config.ConfigValueType.NUMBER) { - bytes = BigInteger.valueOf(((Number) v.unwrapped()).longValue()); - } else if (v.valueType() == com.gu.typesafe.config.ConfigValueType.STRING) { - String s = (String) v.unwrapped(); - bytes = parseBytes(s, v.origin(), path); - } else { - throw new com.gu.typesafe.config.ConfigException.WrongType(v.origin(), path, - "memory size string or number of bytes", v.valueType() - .name()); - } - if (bytes.signum() < 0) - throw new com.gu.typesafe.config.ConfigException.BadValue(v.origin(), path, - "Attempt to construct ConfigMemorySize with negative number: " + bytes); - - result.add(bytes); - } - return result; - } - - @Override - public com.gu.typesafe.config.ConfigMemorySize getMemorySize(String path) { - return com.gu.typesafe.config.ConfigMemorySize.ofBytes(getBytesBigInteger(path)); - } - - @Deprecated - @Override - public Long getMilliseconds(String path) { - return getDuration(path, TimeUnit.MILLISECONDS); - } - - @Deprecated - @Override - public Long getNanoseconds(String path) { - return getDuration(path, TimeUnit.NANOSECONDS); - } - - @Override - public long getDuration(String path, TimeUnit unit) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - long result = unit.convert( - parseDuration((String) v.unwrapped(), v.origin(), path), - TimeUnit.NANOSECONDS); - return result; - } - - @Override - public Duration getDuration(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - long nanos = parseDuration((String) v.unwrapped(), v.origin(), path); - return Duration.ofNanos(nanos); - } - - @Override - public Period getPeriod(String path){ - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.STRING); - return parsePeriod((String) v.unwrapped(), v.origin(), path); - } - - @Override - public TemporalAmount getTemporal(String path){ - try{ - return getDuration(path); - } catch (com.gu.typesafe.config.ConfigException.BadValue e){ - return getPeriod(path); - } - } - - @SuppressWarnings("unchecked") - private List getHomogeneousUnwrappedList(String path, - com.gu.typesafe.config.ConfigValueType expected) { - List l = new ArrayList(); - List list = getList(path); - for (com.gu.typesafe.config.ConfigValue cv : list) { - // variance would be nice, but stupid cast will do - com.gu.typesafe.config.impl.AbstractConfigValue v = (com.gu.typesafe.config.impl.AbstractConfigValue) cv; - if (expected != null) { - v = DefaultTransformer.transform(v, expected); - } - if (v.valueType() != expected) - throw new com.gu.typesafe.config.ConfigException.WrongType(v.origin(), path, - "list of " + expected.name(), "list of " - + v.valueType().name()); - l.add((T) v.unwrapped()); - } - return l; - } - - @Override - public List getBooleanList(String path) { - return getHomogeneousUnwrappedList(path, com.gu.typesafe.config.ConfigValueType.BOOLEAN); - } - - @Override - public List getNumberList(String path) { - return getHomogeneousUnwrappedList(path, com.gu.typesafe.config.ConfigValueType.NUMBER); - } - - @Override - public List getIntList(String path) { - List l = new ArrayList(); - List numbers = getHomogeneousWrappedList(path, com.gu.typesafe.config.ConfigValueType.NUMBER); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : numbers) { - l.add(((com.gu.typesafe.config.impl.ConfigNumber) v).intValueRangeChecked(path)); - } - return l; - } - - @Override - public List getLongList(String path) { - List l = new ArrayList(); - List numbers = getNumberList(path); - for (Number n : numbers) { - l.add(n.longValue()); - } - return l; - } - - @Override - public List getDoubleList(String path) { - List l = new ArrayList(); - List numbers = getNumberList(path); - for (Number n : numbers) { - l.add(n.doubleValue()); - } - return l; - } - - @Override - public List getStringList(String path) { - return getHomogeneousUnwrappedList(path, com.gu.typesafe.config.ConfigValueType.STRING); - } - - @Override - public > List getEnumList(Class enumClass, String path) { - List enumNames = getHomogeneousWrappedList(path, com.gu.typesafe.config.ConfigValueType.STRING); - List enumList = new ArrayList(); - for (ConfigString enumName : enumNames) { - enumList.add(getEnumValue(path, enumClass, enumName)); - } - return enumList; - } - - private > T getEnumValue(String path, Class enumClass, com.gu.typesafe.config.ConfigValue enumConfigValue) { - String enumName = (String) enumConfigValue.unwrapped(); - try { - return Enum.valueOf(enumClass, enumName); - } catch (IllegalArgumentException e) { - List enumNames = new ArrayList(); - Enum[] enumConstants = enumClass.getEnumConstants(); - if (enumConstants != null) { - for (Enum enumConstant : enumConstants) { - enumNames.add(enumConstant.name()); - } - } - throw new com.gu.typesafe.config.ConfigException.BadValue( - enumConfigValue.origin(), path, - String.format("The enum class %s has no constant of the name '%s' (should be one of %s.)", - enumClass.getSimpleName(), enumName, enumNames)); - } - } - - @SuppressWarnings("unchecked") - private List getHomogeneousWrappedList( - String path, com.gu.typesafe.config.ConfigValueType expected) { - List l = new ArrayList(); - List list = getList(path); - for (com.gu.typesafe.config.ConfigValue cv : list) { - // variance would be nice, but stupid cast will do - com.gu.typesafe.config.impl.AbstractConfigValue v = (com.gu.typesafe.config.impl.AbstractConfigValue) cv; - if (expected != null) { - v = DefaultTransformer.transform(v, expected); - } - if (v.valueType() != expected) - throw new com.gu.typesafe.config.ConfigException.WrongType(v.origin(), path, - "list of " + expected.name(), "list of " - + v.valueType().name()); - l.add((T) v); - } - return l; - } - - @Override - public List getObjectList(String path) { - return getHomogeneousWrappedList(path, com.gu.typesafe.config.ConfigValueType.OBJECT); - } - - @Override - public List getConfigList(String path) { - List objects = getObjectList(path); - List l = new ArrayList(); - for (ConfigObject o : objects) { - l.add(o.toConfig()); - } - return l; - } - - @Override - public List getAnyRefList(String path) { - List l = new ArrayList(); - List list = getList(path); - for (com.gu.typesafe.config.ConfigValue v : list) { - l.add(v.unwrapped()); - } - return l; - } - - @Override - public List getBytesList(String path) { - com.gu.typesafe.config.ConfigValue v = find(path, com.gu.typesafe.config.ConfigValueType.LIST); - return getBytesListBigInteger(path).stream() - .map(bytes -> toLong(bytes, v.origin(), path)) - .collect(Collectors.toList()); - } - - private Long toLong(BigInteger value, com.gu.typesafe.config.ConfigOrigin originForException, - String pathForException){ - if (value.bitLength() < 64) { - return value.longValue(); - } else { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, pathForException, - "size-in-bytes value is out of range for a 64-bit long: '" + value + "'"); - } - } - - @Override - public List getMemorySizeList(String path) { - return getBytesListBigInteger(path).stream() - .map(ConfigMemorySize::ofBytes) - .collect(Collectors.toList()); - } - - @Override - public List getDurationList(String path, TimeUnit unit) { - List l = new ArrayList(); - List list = getList(path); - for (com.gu.typesafe.config.ConfigValue v : list) { - if (v.valueType() == com.gu.typesafe.config.ConfigValueType.NUMBER) { - Long n = unit.convert( - ((Number) v.unwrapped()).longValue(), - TimeUnit.MILLISECONDS); - l.add(n); - } else if (v.valueType() == com.gu.typesafe.config.ConfigValueType.STRING) { - String s = (String) v.unwrapped(); - Long n = unit.convert( - parseDuration(s, v.origin(), path), - TimeUnit.NANOSECONDS); - l.add(n); - } else { - throw new com.gu.typesafe.config.ConfigException.WrongType(v.origin(), path, - "duration string or number of milliseconds", - v.valueType().name()); - } - } - return l; - } - - @Override - public List getDurationList(String path) { - List l = getDurationList(path, TimeUnit.NANOSECONDS); - List builder = new ArrayList(l.size()); - for (Long value : l) { - builder.add(Duration.ofNanos(value)); - } - return builder; - } - - @Deprecated - @Override - public List getMillisecondsList(String path) { - return getDurationList(path, TimeUnit.MILLISECONDS); - } - - @Deprecated - @Override - public List getNanosecondsList(String path) { - return getDurationList(path, TimeUnit.NANOSECONDS); - } - - @Override - public AbstractConfigObject toFallbackValue() { - return object; - } - - @Override - public SimpleConfig withFallback(ConfigMergeable other) { - // this can return "this" if the withFallback doesn't need a new - // ConfigObject - return object.withFallback(other).toConfig(); - } - - @Override - public final boolean equals(Object other) { - if (other instanceof SimpleConfig) { - return object.equals(((SimpleConfig) other).object); - } else { - return false; - } - } - - @Override - public final int hashCode() { - // we do the "41*" just so our hash code won't match that of the - // underlying object. there's no real reason it can't match, but - // making it not match might catch some kinds of bug. - return 41 * object.hashCode(); - } - - @Override - public String toString() { - return "Config(" + object.toString() + ")"; - } - - private static String getUnits(String s) { - int i = s.length() - 1; - while (i >= 0) { - char c = s.charAt(i); - if (!Character.isLetter(c)) - break; - i -= 1; - } - return s.substring(i + 1); - } - - /** - * Parses a period string. If no units are specified in the string, it is - * assumed to be in days. The returned period is in days. - * The purpose of this function is to implement the period-related methods - * in the ConfigObject interface. - * - * @param input - * the string to parse - * @param originForException - * origin of the value being parsed - * @param pathForException - * path to include in exceptions - * @return duration in days - * @throws com.gu.typesafe.config.ConfigException - * if string is invalid - */ - public static Period parsePeriod(String input, - com.gu.typesafe.config.ConfigOrigin originForException, String pathForException) { - String s = ConfigImplUtil.unicodeTrim(input); - String originalUnitString = getUnits(s); - String unitString = originalUnitString; - String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length() - - unitString.length())); - ChronoUnit units; - - // this would be caught later anyway, but the error message - // is more helpful if we check it here. - if (numberString.length() == 0) - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "No number in period value '" + input - + "'"); - - if (unitString.length() > 2 && !unitString.endsWith("s")) - unitString = unitString + "s"; - - // note that this is deliberately case-sensitive - if (unitString.equals("") || unitString.equals("d") || unitString.equals("days")) { - units = ChronoUnit.DAYS; - - } else if (unitString.equals("w") || unitString.equals("weeks")) { - units = ChronoUnit.WEEKS; - - } else if (unitString.equals("m") || unitString.equals("mo") || unitString.equals("months")) { - units = ChronoUnit.MONTHS; - - } else if (unitString.equals("y") || unitString.equals("years")) { - units = ChronoUnit.YEARS; - - } else { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "Could not parse time unit '" - + originalUnitString - + "' (try d, w, mo, y)"); - } - - try { - return periodOf(Integer.parseInt(numberString), units); - } catch (NumberFormatException e) { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "Could not parse duration number '" - + numberString + "'"); - } - } - - - private static Period periodOf(int n, ChronoUnit unit){ - if(unit.isTimeBased()){ - throw new DateTimeException(unit + " cannot be converted to a java.time.Period"); - } - - switch (unit){ - case DAYS: - return Period.ofDays(n); - case WEEKS: - return Period.ofWeeks(n); - case MONTHS: - return Period.ofMonths(n); - case YEARS: - return Period.ofYears(n); - default: - throw new DateTimeException(unit + " cannot be converted to a java.time.Period"); - } - } - - /** - * Parses a duration string. If no units are specified in the string, it is - * assumed to be in milliseconds. The returned duration is in nanoseconds. - * The purpose of this function is to implement the duration-related methods - * in the ConfigObject interface. - * - * @param input - * the string to parse - * @param originForException - * origin of the value being parsed - * @param pathForException - * path to include in exceptions - * @return duration in nanoseconds - * @throws com.gu.typesafe.config.ConfigException - * if string is invalid - */ - public static long parseDuration(String input, - com.gu.typesafe.config.ConfigOrigin originForException, String pathForException) { - String s = ConfigImplUtil.unicodeTrim(input); - String originalUnitString = getUnits(s); - String unitString = originalUnitString; - String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length() - - unitString.length())); - TimeUnit units = null; - - // this would be caught later anyway, but the error message - // is more helpful if we check it here. - if (numberString.length() == 0) - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "No number in duration value '" + input - + "'"); - - if (unitString.length() > 2 && !unitString.endsWith("s")) - unitString = unitString + "s"; - - // note that this is deliberately case-sensitive - if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis") - || unitString.equals("milliseconds")) { - units = TimeUnit.MILLISECONDS; - } else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) { - units = TimeUnit.MICROSECONDS; - } else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) { - units = TimeUnit.NANOSECONDS; - } else if (unitString.equals("d") || unitString.equals("days")) { - units = TimeUnit.DAYS; - } else if (unitString.equals("h") || unitString.equals("hours")) { - units = TimeUnit.HOURS; - } else if (unitString.equals("s") || unitString.equals("seconds")) { - units = TimeUnit.SECONDS; - } else if (unitString.equals("m") || unitString.equals("minutes")) { - units = TimeUnit.MINUTES; - } else { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "Could not parse time unit '" - + originalUnitString - + "' (try ns, us, ms, s, m, h, d)"); - } - - try { - // if the string is purely digits, parse as an integer to avoid - // possible precision loss; - // otherwise as a double. - if (numberString.matches("[+-]?[0-9]+")) { - return units.toNanos(Long.parseLong(numberString)); - } else { - long nanosInUnit = units.toNanos(1); - return (long) (Double.parseDouble(numberString) * nanosInUnit); - } - } catch (NumberFormatException e) { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "Could not parse duration number '" - + numberString + "'"); - } - } - - private static enum MemoryUnit { - BYTES("", 1024, 0), - - KILOBYTES("kilo", 1000, 1), - MEGABYTES("mega", 1000, 2), - GIGABYTES("giga", 1000, 3), - TERABYTES("tera", 1000, 4), - PETABYTES("peta", 1000, 5), - EXABYTES("exa", 1000, 6), - ZETTABYTES("zetta", 1000, 7), - YOTTABYTES("yotta", 1000, 8), - - KIBIBYTES("kibi", 1024, 1), - MEBIBYTES("mebi", 1024, 2), - GIBIBYTES("gibi", 1024, 3), - TEBIBYTES("tebi", 1024, 4), - PEBIBYTES("pebi", 1024, 5), - EXBIBYTES("exbi", 1024, 6), - ZEBIBYTES("zebi", 1024, 7), - YOBIBYTES("yobi", 1024, 8); - - final String prefix; - final int powerOf; - final int power; - final BigInteger bytes; - - MemoryUnit(String prefix, int powerOf, int power) { - this.prefix = prefix; - this.powerOf = powerOf; - this.power = power; - this.bytes = BigInteger.valueOf(powerOf).pow(power); - } - - private static Map makeUnitsMap() { - Map map = new HashMap(); - for (MemoryUnit unit : MemoryUnit.values()) { - map.put(unit.prefix + "byte", unit); - map.put(unit.prefix + "bytes", unit); - if (unit.prefix.length() == 0) { - map.put("b", unit); - map.put("B", unit); - map.put("", unit); // no unit specified means bytes - } else { - String first = unit.prefix.substring(0, 1); - String firstUpper = first.toUpperCase(); - if (unit.powerOf == 1024) { - map.put(first, unit); // 512m - map.put(firstUpper, unit); // 512M - map.put(firstUpper + "i", unit); // 512Mi - map.put(firstUpper + "iB", unit); // 512MiB - } else if (unit.powerOf == 1000) { - if (unit.power == 1) { - map.put(first + "B", unit); // 512kB - } else { - map.put(firstUpper + "B", unit); // 512MB - } - } else { - throw new RuntimeException("broken MemoryUnit enum"); - } - } - } - return map; - } - - private static Map unitsMap = makeUnitsMap(); - - static MemoryUnit parseUnit(String unit) { - return unitsMap.get(unit); - } - } - - /** - * Parses a size-in-bytes string. If no units are specified in the string, - * it is assumed to be in bytes. The returned value is in bytes. The purpose - * of this function is to implement the size-in-bytes-related methods in the - * Config interface. - * - * @param input - * the string to parse - * @param originForException - * origin of the value being parsed - * @param pathForException - * path to include in exceptions - * @return size in bytes - * @throws com.gu.typesafe.config.ConfigException - * if string is invalid - */ - public static BigInteger parseBytes(String input, com.gu.typesafe.config.ConfigOrigin originForException, - String pathForException) { - String s = ConfigImplUtil.unicodeTrim(input); - String unitString = getUnits(s); - String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, - s.length() - unitString.length())); - - // this would be caught later anyway, but the error message - // is more helpful if we check it here. - if (numberString.length() == 0) - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, - pathForException, "No number in size-in-bytes value '" - + input + "'"); - - MemoryUnit units = MemoryUnit.parseUnit(unitString); - - if (units == null) { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, pathForException, - "Could not parse size-in-bytes unit '" + unitString - + "' (try k, K, kB, KiB, kilobytes, kibibytes)"); - } - - try { - BigInteger result; - // if the string is purely digits, parse as an integer to avoid - // possible precision loss; otherwise as a double. - if (numberString.matches("[0-9]+")) { - result = units.bytes.multiply(new BigInteger(numberString)); - } else { - BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString)); - result = resultDecimal.toBigInteger(); - } - return result; - } catch (NumberFormatException e) { - throw new com.gu.typesafe.config.ConfigException.BadValue(originForException, pathForException, - "Could not parse size-in-bytes number '" + numberString + "'"); - } - } - - private com.gu.typesafe.config.impl.AbstractConfigValue peekPath(Path path) { - return root().peekPath(path); - } - - private static void addProblem(List accumulator, Path path, - com.gu.typesafe.config.ConfigOrigin origin, String problem) { - accumulator.add(new com.gu.typesafe.config.ConfigException.ValidationProblem(path.render(), origin, problem)); - } - - private static String getDesc(com.gu.typesafe.config.ConfigValueType type) { - return type.name().toLowerCase(); - } - - private static String getDesc(com.gu.typesafe.config.ConfigValue refValue) { - if (refValue instanceof AbstractConfigObject) { - AbstractConfigObject obj = (AbstractConfigObject) refValue; - if (!obj.isEmpty()) - return "object with keys " + obj.keySet(); - else - return getDesc(refValue.valueType()); - } else { - return getDesc(refValue.valueType()); - } - } - - private static void addMissing(List accumulator, - String refDesc, Path path, com.gu.typesafe.config.ConfigOrigin origin) { - addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: " - + refDesc); - } - - private static void addMissing(List accumulator, - com.gu.typesafe.config.ConfigValue refValue, Path path, com.gu.typesafe.config.ConfigOrigin origin) { - addMissing(accumulator, getDesc(refValue), path, origin); - } - - // JavaBean stuff uses this - static void addMissing(List accumulator, - com.gu.typesafe.config.ConfigValueType refType, Path path, com.gu.typesafe.config.ConfigOrigin origin) { - addMissing(accumulator, getDesc(refType), path, origin); - } - - private static void addWrongType(List accumulator, - String refDesc, com.gu.typesafe.config.impl.AbstractConfigValue actual, Path path) { - addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render() - + "', expecting: " + refDesc + " but got: " - + getDesc(actual)); - } - - private static void addWrongType(List accumulator, - com.gu.typesafe.config.ConfigValue refValue, com.gu.typesafe.config.impl.AbstractConfigValue actual, Path path) { - addWrongType(accumulator, getDesc(refValue), actual, path); - } - - private static void addWrongType(List accumulator, - com.gu.typesafe.config.ConfigValueType refType, com.gu.typesafe.config.impl.AbstractConfigValue actual, Path path) { - addWrongType(accumulator, getDesc(refType), actual, path); - } - - private static boolean couldBeNull(com.gu.typesafe.config.impl.AbstractConfigValue v) { - return DefaultTransformer.transform(v, com.gu.typesafe.config.ConfigValueType.NULL) - .valueType() == com.gu.typesafe.config.ConfigValueType.NULL; - } - - private static boolean haveCompatibleTypes(com.gu.typesafe.config.ConfigValue reference, com.gu.typesafe.config.impl.AbstractConfigValue value) { - if (couldBeNull((com.gu.typesafe.config.impl.AbstractConfigValue) reference)) { - // we allow any setting to be null - return true; - } else { - return haveCompatibleTypes(reference.valueType(), value); - } - } - - private static boolean haveCompatibleTypes(com.gu.typesafe.config.ConfigValueType referenceType, com.gu.typesafe.config.impl.AbstractConfigValue value) { - if (referenceType == com.gu.typesafe.config.ConfigValueType.NULL || couldBeNull(value)) { - // we allow any setting to be null - return true; - } else if (referenceType == com.gu.typesafe.config.ConfigValueType.OBJECT) { - if (value instanceof AbstractConfigObject) { - return true; - } else { - return false; - } - } else if (referenceType == com.gu.typesafe.config.ConfigValueType.LIST) { - // objects may be convertible to lists if they have numeric keys - if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) { - return true; - } else { - return false; - } - } else if (referenceType == com.gu.typesafe.config.ConfigValueType.STRING) { - // assume a string could be gotten as any non-collection type; - // allows things like getMilliseconds including domain-specific - // interpretations of strings - return true; - } else if (value instanceof ConfigString) { - // assume a string could be gotten as any non-collection type - return true; - } else { - if (referenceType == value.valueType()) { - return true; - } else { - return false; - } - } - } - - // path is null if we're at the root - private static void checkValidObject(Path path, AbstractConfigObject reference, - AbstractConfigObject value, - List accumulator) { - for (Map.Entry entry : reference.entrySet()) { - String key = entry.getKey(); - - Path childPath; - if (path != null) - childPath = Path.newKey(key).prepend(path); - else - childPath = Path.newKey(key); - - com.gu.typesafe.config.impl.AbstractConfigValue v = value.get(key); - if (v == null) { - addMissing(accumulator, entry.getValue(), childPath, value.origin()); - } else { - checkValid(childPath, entry.getValue(), v, accumulator); - } - } - } - - private static void checkListCompatibility(Path path, SimpleConfigList listRef, - SimpleConfigList listValue, List accumulator) { - if (listRef.isEmpty() || listValue.isEmpty()) { - // can't verify type, leave alone - } else { - com.gu.typesafe.config.impl.AbstractConfigValue refElement = listRef.get(0); - for (com.gu.typesafe.config.ConfigValue elem : listValue) { - com.gu.typesafe.config.impl.AbstractConfigValue e = (com.gu.typesafe.config.impl.AbstractConfigValue) elem; - if (!haveCompatibleTypes(refElement, e)) { - addProblem(accumulator, path, e.origin(), "List at '" + path.render() - + "' contains wrong value type, expecting list of " - + getDesc(refElement) + " but got element of type " + getDesc(e)); - // don't add a problem for every last array element - break; - } - } - } - } - - // Used by the JavaBean-based validator - static void checkValid(Path path, com.gu.typesafe.config.ConfigValueType referenceType, com.gu.typesafe.config.impl.AbstractConfigValue value, - List accumulator) { - if (haveCompatibleTypes(referenceType, value)) { - if (referenceType == com.gu.typesafe.config.ConfigValueType.LIST && value instanceof SimpleConfigObject) { - // attempt conversion of indexed object to list - com.gu.typesafe.config.impl.AbstractConfigValue listValue = DefaultTransformer.transform(value, - com.gu.typesafe.config.ConfigValueType.LIST); - if (!(listValue instanceof SimpleConfigList)) - addWrongType(accumulator, referenceType, value, path); - } - } else { - addWrongType(accumulator, referenceType, value, path); - } - } - - private static void checkValid(Path path, com.gu.typesafe.config.ConfigValue reference, com.gu.typesafe.config.impl.AbstractConfigValue value, - List accumulator) { - // Unmergeable is supposed to be impossible to encounter in here - // because we check for resolve status up front. - - if (haveCompatibleTypes(reference, value)) { - if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) { - checkValidObject(path, (AbstractConfigObject) reference, - (AbstractConfigObject) value, accumulator); - } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) { - SimpleConfigList listRef = (SimpleConfigList) reference; - SimpleConfigList listValue = (SimpleConfigList) value; - checkListCompatibility(path, listRef, listValue, accumulator); - } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigObject) { - // attempt conversion of indexed object to list - SimpleConfigList listRef = (SimpleConfigList) reference; - com.gu.typesafe.config.impl.AbstractConfigValue listValue = DefaultTransformer.transform(value, - ConfigValueType.LIST); - if (listValue instanceof SimpleConfigList) - checkListCompatibility(path, listRef, (SimpleConfigList) listValue, accumulator); - else - addWrongType(accumulator, reference, value, path); - } - } else { - addWrongType(accumulator, reference, value, path); - } - } - - @Override - public boolean isResolved() { - return root().resolveStatus() == ResolveStatus.RESOLVED; - } - - @Override - public void checkValid(com.gu.typesafe.config.Config reference, String... restrictToPaths) { - SimpleConfig ref = (SimpleConfig) reference; - - // unresolved reference config is a bug in the caller of checkValid - if (ref.root().resolveStatus() != ResolveStatus.RESOLVED) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "do not call checkValid() with an unresolved reference config, call Config#resolve(), see Config#resolve() API docs"); - - // unresolved config under validation is a bug in something, - // NotResolved is a more specific subclass of BugOrBroken - if (root().resolveStatus() != ResolveStatus.RESOLVED) - throw new com.gu.typesafe.config.ConfigException.NotResolved( - "need to Config#resolve() each config before using it, see the API docs for Config#resolve()"); - - // Now we know that both reference and this config are resolved - - List problems = new ArrayList(); - - if (restrictToPaths.length == 0) { - checkValidObject(null, ref.root(), root(), problems); - } else { - for (String p : restrictToPaths) { - Path path = Path.newPath(p); - com.gu.typesafe.config.impl.AbstractConfigValue refValue = ref.peekPath(path); - if (refValue != null) { - com.gu.typesafe.config.impl.AbstractConfigValue child = peekPath(path); - if (child != null) { - checkValid(path, refValue, child, problems); - } else { - addMissing(problems, refValue, path, origin()); - } - } - } - } - - if (!problems.isEmpty()) { - throw new ConfigException.ValidationFailed(problems); - } - } - - @Override - public SimpleConfig withOnlyPath(String pathExpression) { - Path path = Path.newPath(pathExpression); - return new SimpleConfig(root().withOnlyPath(path)); - } - - @Override - public SimpleConfig withoutPath(String pathExpression) { - Path path = Path.newPath(pathExpression); - return new SimpleConfig(root().withoutPath(path)); - } - - @Override - public SimpleConfig withValue(String pathExpression, ConfigValue v) { - Path path = Path.newPath(pathExpression); - return new SimpleConfig(root().withValue(path, v)); - } - - SimpleConfig atKey(ConfigOrigin origin, String key) { - return root().atKey(origin, key); - } - - @Override - public SimpleConfig atKey(String key) { - return root().atKey(key); - } - - @Override - public Config atPath(String path) { - return root().atPath(path); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigDocument.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigDocument.java deleted file mode 100644 index 1a522ed3236..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigDocument.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigRenderOptions; -import com.gu.typesafe.config.parser.ConfigDocument; - -import java.io.StringReader; -import java.util.Iterator; - -final class SimpleConfigDocument implements com.gu.typesafe.config.parser.ConfigDocument { - private ConfigNodeRoot configNodeTree; - private com.gu.typesafe.config.ConfigParseOptions parseOptions; - - SimpleConfigDocument(ConfigNodeRoot parsedNode, com.gu.typesafe.config.ConfigParseOptions parseOptions) { - configNodeTree = parsedNode; - this.parseOptions = parseOptions; - } - - @Override - public com.gu.typesafe.config.parser.ConfigDocument withValueText(String path, String newValue) { - if (newValue == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("null value for " + path + " passed to withValueText"); - SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("single value parsing"); - StringReader reader = new StringReader(newValue); - Iterator tokens = Tokenizer.tokenize(origin, reader, parseOptions.getSyntax()); - com.gu.typesafe.config.impl.AbstractConfigNodeValue parsedValue = ConfigDocumentParser.parseValue(tokens, origin, parseOptions); - reader.close(); - - return new SimpleConfigDocument(configNodeTree.setValue(path, parsedValue, parseOptions.getSyntax()), parseOptions); - } - - @Override - public com.gu.typesafe.config.parser.ConfigDocument withValue(String path, com.gu.typesafe.config.ConfigValue newValue) { - if (newValue == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("null value for " + path + " passed to withValue"); - com.gu.typesafe.config.ConfigRenderOptions options = ConfigRenderOptions.defaults(); - options = options.setOriginComments(false); - return withValueText(path, newValue.render(options).trim()); - } - - @Override - public com.gu.typesafe.config.parser.ConfigDocument withoutPath(String path) { - return new SimpleConfigDocument(configNodeTree.setValue(path, null, parseOptions.getSyntax()), parseOptions); - } - - @Override - public boolean hasPath(String path) { - return configNodeTree.hasValue(path); - } - - public String render() { - return configNodeTree.render(); - } - - @Override - public boolean equals(Object other) { - return other instanceof com.gu.typesafe.config.parser.ConfigDocument && render().equals(((ConfigDocument) other).render()); - } - - @Override - public int hashCode() { - return render().hashCode(); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigList.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigList.java deleted file mode 100644 index b6cdb8d9967..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigList.java +++ /dev/null @@ -1,460 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import com.gu.typesafe.config.*; -import com.gu.typesafe.config.ConfigRenderOptions; - -final class SimpleConfigList extends com.gu.typesafe.config.impl.AbstractConfigValue implements ConfigList, com.gu.typesafe.config.impl.Container, Serializable { - - private static final long serialVersionUID = 2L; - - final private List value; - final private boolean resolved; - - SimpleConfigList(com.gu.typesafe.config.ConfigOrigin origin, List value) { - this(origin, value, ResolveStatus - .fromValues(value)); - } - - SimpleConfigList(com.gu.typesafe.config.ConfigOrigin origin, List value, - ResolveStatus status) { - super(origin); - this.value = value; - this.resolved = status == ResolveStatus.RESOLVED; - - // kind of an expensive debug check (makes this constructor pointless) - if (status != ResolveStatus.fromValues(value)) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "SimpleConfigList created with wrong resolve status: " + this); - } - - @Override - public com.gu.typesafe.config.ConfigValueType valueType() { - return ConfigValueType.LIST; - } - - @Override - public List unwrapped() { - List list = new ArrayList(); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : value) { - list.add(v.unwrapped()); - } - return list; - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.fromBoolean(resolved); - } - - @Override - public SimpleConfigList replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - List newList = replaceChildInList(value, child, replacement); - if (newList == null) { - return null; - } else { - // we use the constructor flavor that will recompute the resolve - // status - return new SimpleConfigList(origin(), newList); - } - } - - @Override - public boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant) { - return hasDescendantInList(value, descendant); - } - - private SimpleConfigList modify(NoExceptionsModifier modifier, ResolveStatus newResolveStatus) { - try { - return modifyMayThrow(modifier, newResolveStatus); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("unexpected checked exception", e); - } - } - - private SimpleConfigList modifyMayThrow(Modifier modifier, ResolveStatus newResolveStatus) - throws Exception { - // lazy-create for optimization - List changed = null; - int i = 0; - for (com.gu.typesafe.config.impl.AbstractConfigValue v : value) { - com.gu.typesafe.config.impl.AbstractConfigValue modified = modifier.modifyChildMayThrow(null /* key */, v); - - // lazy-create the new list if required - if (changed == null && modified != v) { - changed = new ArrayList(); - for (int j = 0; j < i; ++j) { - changed.add(value.get(j)); - } - } - - // once the new list is created, all elements - // have to go in it. if modifyChild returned - // null, we drop that element. - if (changed != null && modified != null) { - changed.add(modified); - } - - i += 1; - } - - if (changed != null) { - if (newResolveStatus != null) { - return new SimpleConfigList(origin(), changed, newResolveStatus); - } else { - return new SimpleConfigList(origin(), changed); - } - } else { - return this; - } - } - - private static class ResolveModifier implements Modifier { - com.gu.typesafe.config.impl.ResolveContext context; - final com.gu.typesafe.config.impl.ResolveSource source; - ResolveModifier(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) { - this.context = context; - this.source = source; - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue modifyChildMayThrow(String key, com.gu.typesafe.config.impl.AbstractConfigValue v) - throws NotPossibleToResolve { - com.gu.typesafe.config.impl.ResolveResult result = context.resolve(v, source); - context = result.context; - return result.value; - } - } - - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) - throws NotPossibleToResolve { - if (resolved) - return com.gu.typesafe.config.impl.ResolveResult.make(context, this); - - if (context.isRestrictedToChild()) { - // if a list restricts to a child path, then it has no child paths, - // so nothing to do. - return com.gu.typesafe.config.impl.ResolveResult.make(context, this); - } else { - try { - ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this)); - SimpleConfigList value = modifyMayThrow(modifier, context.options().getAllowUnresolved() ? null : ResolveStatus.RESOLVED); - return com.gu.typesafe.config.impl.ResolveResult.make(modifier.context, value); - } catch (NotPossibleToResolve e) { - throw e; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new ConfigException.BugOrBroken("unexpected checked exception", e); - } - } - } - - @Override - SimpleConfigList relativized(final Path prefix) { - return modify(new NoExceptionsModifier() { - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue modifyChild(String key, com.gu.typesafe.config.impl.AbstractConfigValue v) { - return v.relativized(prefix); - } - - }, resolveStatus()); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof SimpleConfigList; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality - if (other instanceof SimpleConfigList) { - // optimization to avoid unwrapped() for two ConfigList - return canEqual(other) - && (value == ((SimpleConfigList) other).value || value.equals(((SimpleConfigList) other).value)); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - return value.hashCode(); - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - if (value.isEmpty()) { - sb.append("[]"); - } else { - sb.append("["); - if (options.getFormatted()) - sb.append('\n'); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : value) { - if (options.getOriginComments()) { - String[] lines = v.origin().description().split("\n"); - for (String l : lines) { - indent(sb, indent + 1, options); - sb.append('#'); - if (!l.isEmpty()) - sb.append(' '); - sb.append(l); - sb.append("\n"); - } - } - if (options.getComments()) { - for (String comment : v.origin().comments()) { - indent(sb, indent + 1, options); - sb.append("# "); - sb.append(comment); - sb.append("\n"); - } - } - indent(sb, indent + 1, options); - - v.render(sb, indent + 1, atRoot, options); - sb.append(","); - if (options.getFormatted()) - sb.append('\n'); - } - sb.setLength(sb.length() - 1); // chop or newline - if (options.getFormatted()) { - sb.setLength(sb.length() - 1); // also chop comma - sb.append('\n'); - indent(sb, indent, options); - } - sb.append("]"); - } - } - - @Override - public boolean contains(Object o) { - return value.contains(o); - } - - @Override - public boolean containsAll(Collection c) { - return value.containsAll(c); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue get(int index) { - return value.get(index); - } - - @Override - public int indexOf(Object o) { - return value.indexOf(o); - } - - @Override - public boolean isEmpty() { - return value.isEmpty(); - } - - @Override - public Iterator iterator() { - final Iterator i = value.iterator(); - - return new Iterator() { - @Override - public boolean hasNext() { - return i.hasNext(); - } - - @Override - public com.gu.typesafe.config.ConfigValue next() { - return i.next(); - } - - @Override - public void remove() { - throw weAreImmutable("iterator().remove"); - } - }; - } - - @Override - public int lastIndexOf(Object o) { - return value.lastIndexOf(o); - } - - private static ListIterator wrapListIterator( - final ListIterator i) { - return new ListIterator() { - @Override - public boolean hasNext() { - return i.hasNext(); - } - - @Override - public com.gu.typesafe.config.ConfigValue next() { - return i.next(); - } - - @Override - public void remove() { - throw weAreImmutable("listIterator().remove"); - } - - @Override - public void add(com.gu.typesafe.config.ConfigValue arg0) { - throw weAreImmutable("listIterator().add"); - } - - @Override - public boolean hasPrevious() { - return i.hasPrevious(); - } - - @Override - public int nextIndex() { - return i.nextIndex(); - } - - @Override - public com.gu.typesafe.config.ConfigValue previous() { - return i.previous(); - } - - @Override - public int previousIndex() { - return i.previousIndex(); - } - - @Override - public void set(com.gu.typesafe.config.ConfigValue arg0) { - throw weAreImmutable("listIterator().set"); - } - }; - } - - @Override - public ListIterator listIterator() { - return wrapListIterator(value.listIterator()); - } - - @Override - public ListIterator listIterator(int index) { - return wrapListIterator(value.listIterator(index)); - } - - @Override - public int size() { - return value.size(); - } - - @Override - public List subList(int fromIndex, int toIndex) { - List list = new ArrayList(); - // yay bloat caused by lack of type variance - for (com.gu.typesafe.config.impl.AbstractConfigValue v : value.subList(fromIndex, toIndex)) { - list.add(v); - } - return list; - } - - @Override - public Object[] toArray() { - return value.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return value.toArray(a); - } - - private static UnsupportedOperationException weAreImmutable(String method) { - return new UnsupportedOperationException( - "ConfigList is immutable, you can't call List.'" + method + "'"); - } - - @Override - public boolean add(com.gu.typesafe.config.ConfigValue e) { - throw weAreImmutable("add"); - } - - @Override - public void add(int index, com.gu.typesafe.config.ConfigValue element) { - throw weAreImmutable("add"); - } - - @Override - public boolean addAll(Collection c) { - throw weAreImmutable("addAll"); - } - - @Override - public boolean addAll(int index, Collection c) { - throw weAreImmutable("addAll"); - } - - @Override - public void clear() { - throw weAreImmutable("clear"); - } - - @Override - public boolean remove(Object o) { - throw weAreImmutable("remove"); - } - - @Override - public com.gu.typesafe.config.ConfigValue remove(int index) { - throw weAreImmutable("remove"); - } - - @Override - public boolean removeAll(Collection c) { - throw weAreImmutable("removeAll"); - } - - @Override - public boolean retainAll(Collection c) { - throw weAreImmutable("retainAll"); - } - - @Override - public com.gu.typesafe.config.ConfigValue set(int index, ConfigValue element) { - throw weAreImmutable("set"); - } - - @Override - protected SimpleConfigList newCopy(com.gu.typesafe.config.ConfigOrigin newOrigin) { - return new SimpleConfigList(newOrigin, value); - } - - final SimpleConfigList concatenate(SimpleConfigList other) { - com.gu.typesafe.config.ConfigOrigin combinedOrigin = com.gu.typesafe.config.impl.SimpleConfigOrigin.mergeOrigins(origin(), other.origin()); - List combined = new ArrayList(value.size() - + other.value.size()); - combined.addAll(value); - combined.addAll(other.value); - return new SimpleConfigList(combinedOrigin, combined); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } - - @Override - public SimpleConfigList withOrigin(ConfigOrigin origin) { - return (SimpleConfigList) super.withOrigin(origin); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigObject.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigObject.java deleted file mode 100644 index 42b04304b61..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigObject.java +++ /dev/null @@ -1,674 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - * THIS FILE HAS BEEN MODIFIED TO ADD SUPPORT FOR COMPACT KEYS - */ -package com.gu.typesafe.config.impl; - -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.math.BigInteger; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.gu.typesafe.config.*; - -final class SimpleConfigObject extends AbstractConfigObject implements Serializable { - - private static final long serialVersionUID = 2L; - - // this map should never be modified - assume immutable - final private Map value; - final private boolean resolved; - final private boolean ignoresFallbacks; - - SimpleConfigObject(com.gu.typesafe.config.ConfigOrigin origin, - Map value, ResolveStatus status, - boolean ignoresFallbacks) { - super(origin); - if (value == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "creating config object with null map"); - this.value = value; - this.resolved = status == ResolveStatus.RESOLVED; - this.ignoresFallbacks = ignoresFallbacks; - - // Kind of an expensive debug check. Comment out? - if (status != ResolveStatus.fromValues(value.values())) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("Wrong resolved status on " + this); - } - - SimpleConfigObject(com.gu.typesafe.config.ConfigOrigin origin, - Map value) { - this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */); - } - - @Override - public SimpleConfigObject withOnlyKey(String key) { - return withOnlyPath(Path.newKey(key)); - } - - @Override - public SimpleConfigObject withoutKey(String key) { - return withoutPath(Path.newKey(key)); - } - - // gets the object with only the path if the path - // exists, otherwise null if it doesn't. this ensures - // that if we have { a : { b : 42 } } and do - // withOnlyPath("a.b.c") that we don't keep an empty - // "a" object. - @Override - protected SimpleConfigObject withOnlyPathOrNull(Path path) { - String key = path.first(); - Path next = path.remainder(); - com.gu.typesafe.config.impl.AbstractConfigValue v = value.get(key); - - if (next != null) { - if (v != null && (v instanceof AbstractConfigObject)) { - v = ((AbstractConfigObject) v).withOnlyPathOrNull(next); - } else { - // if the path has more elements but we don't have an object, - // then the rest of the path does not exist. - v = null; - } - } - - if (v == null) { - return null; - } else { - return new SimpleConfigObject(origin(), Collections.singletonMap(key, v), - v.resolveStatus(), ignoresFallbacks); - } - } - - @Override - SimpleConfigObject withOnlyPath(Path path) { - SimpleConfigObject o = withOnlyPathOrNull(path); - if (o == null) { - return new SimpleConfigObject(origin(), - Collections. emptyMap(), ResolveStatus.RESOLVED, - ignoresFallbacks); - } else { - return o; - } - } - - @Override - SimpleConfigObject withoutPath(Path path) { - String key = path.first(); - Path next = path.remainder(); - com.gu.typesafe.config.impl.AbstractConfigValue v = value.get(key); - - if (v != null && next != null && v instanceof AbstractConfigObject) { - v = ((AbstractConfigObject) v).withoutPath(next); - Map updated = new HashMap( - value); - updated.put(key, v); - return new SimpleConfigObject(origin(), updated, ResolveStatus.fromValues(updated - .values()), ignoresFallbacks); - } else if (next != null || v == null) { - // can't descend, nothing to remove - return this; - } else { - Map smaller = new HashMap( - value.size() - 1); - for (Map.Entry old : value.entrySet()) { - if (!old.getKey().equals(key)) - smaller.put(old.getKey(), old.getValue()); - } - return new SimpleConfigObject(origin(), smaller, ResolveStatus.fromValues(smaller - .values()), ignoresFallbacks); - } - } - - @Override - public SimpleConfigObject withValue(String key, com.gu.typesafe.config.ConfigValue v) { - if (v == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "Trying to store null ConfigValue in a ConfigObject"); - - Map newMap; - if (value.isEmpty()) { - newMap = Collections.singletonMap(key, (com.gu.typesafe.config.impl.AbstractConfigValue) v); - } else { - newMap = new HashMap(value); - newMap.put(key, (com.gu.typesafe.config.impl.AbstractConfigValue) v); - } - - return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()), - ignoresFallbacks); - } - - @Override - SimpleConfigObject withValue(Path path, com.gu.typesafe.config.ConfigValue v) { - String key = path.first(); - Path next = path.remainder(); - - if (next == null) { - return withValue(key, v); - } else { - com.gu.typesafe.config.impl.AbstractConfigValue child = value.get(key); - if (child != null && child instanceof AbstractConfigObject) { - // if we have an object, add to it - return withValue(key, ((AbstractConfigObject) child).withValue(next, v)); - } else { - // as soon as we have a non-object, replace it entirely - com.gu.typesafe.config.impl.SimpleConfig subtree = ((com.gu.typesafe.config.impl.AbstractConfigValue) v).atPath( - com.gu.typesafe.config.impl.SimpleConfigOrigin.newSimple("withValue(" + next.render() + ")"), next); - return withValue(key, subtree.root()); - } - } - } - - @Override - protected com.gu.typesafe.config.impl.AbstractConfigValue attemptPeekWithPartialResolve(String key) { - return value.get(key); - } - - private SimpleConfigObject newCopy(ResolveStatus newStatus, com.gu.typesafe.config.ConfigOrigin newOrigin, - boolean newIgnoresFallbacks) { - return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks); - } - - @Override - protected SimpleConfigObject newCopy(ResolveStatus newStatus, com.gu.typesafe.config.ConfigOrigin newOrigin) { - return newCopy(newStatus, newOrigin, ignoresFallbacks); - } - - @Override - protected SimpleConfigObject withFallbacksIgnored() { - if (ignoresFallbacks) - return this; - else - return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */); - } - - @Override - ResolveStatus resolveStatus() { - return ResolveStatus.fromBoolean(resolved); - } - - @Override - public SimpleConfigObject replaceChild(com.gu.typesafe.config.impl.AbstractConfigValue child, com.gu.typesafe.config.impl.AbstractConfigValue replacement) { - HashMap newChildren = new HashMap(value); - for (Map.Entry old : newChildren.entrySet()) { - if (old.getValue() == child) { - if (replacement != null) - old.setValue(replacement); - else - newChildren.remove(old.getKey()); - - return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values()), - ignoresFallbacks); - } - } - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("SimpleConfigObject.replaceChild did not find " + child + " in " + this); - } - - @Override - public boolean hasDescendant(com.gu.typesafe.config.impl.AbstractConfigValue descendant) { - for (com.gu.typesafe.config.impl.AbstractConfigValue child : value.values()) { - if (child == descendant) - return true; - } - // now do the expensive search - for (com.gu.typesafe.config.impl.AbstractConfigValue child : value.values()) { - if (child instanceof com.gu.typesafe.config.impl.Container && ((com.gu.typesafe.config.impl.Container) child).hasDescendant(descendant)) - return true; - } - - return false; - } - - @Override - protected boolean ignoresFallbacks() { - return ignoresFallbacks; - } - - @Override - public Map unwrapped() { - Map m = new HashMap(); - for (Map.Entry e : value.entrySet()) { - m.put(e.getKey(), e.getValue().unwrapped()); - } - return m; - } - - @Override - protected SimpleConfigObject mergedWithObject(AbstractConfigObject abstractFallback) { - requireNotIgnoringFallbacks(); - - if (!(abstractFallback instanceof SimpleConfigObject)) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "should not be reached (merging non-SimpleConfigObject)"); - } - - SimpleConfigObject fallback = (SimpleConfigObject) abstractFallback; - - boolean changed = false; - boolean allResolved = true; - Map merged = new HashMap(); - Set allKeys = new HashSet(); - allKeys.addAll(this.keySet()); - allKeys.addAll(fallback.keySet()); - for (String key : allKeys) { - com.gu.typesafe.config.impl.AbstractConfigValue first = this.value.get(key); - com.gu.typesafe.config.impl.AbstractConfigValue second = fallback.value.get(key); - com.gu.typesafe.config.impl.AbstractConfigValue kept; - if (first == null) - kept = second; - else if (second == null) - kept = first; - else - kept = first.withFallback(second); - - merged.put(key, kept); - - if (first != kept) - changed = true; - - if (kept.resolveStatus() == ResolveStatus.UNRESOLVED) - allResolved = false; - } - - ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved); - boolean newIgnoresFallbacks = fallback.ignoresFallbacks(); - - if (changed) - return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, - newIgnoresFallbacks); - else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) - return newCopy(newResolveStatus, origin(), newIgnoresFallbacks); - else - return this; - } - - private SimpleConfigObject modify(NoExceptionsModifier modifier) { - try { - return modifyMayThrow(modifier); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("unexpected checked exception", e); - } - } - - private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception { - Map changes = null; - for (String k : keySet()) { - com.gu.typesafe.config.impl.AbstractConfigValue v = value.get(k); - // "modified" may be null, which means remove the child; - // to do that we put null in the "changes" map. - com.gu.typesafe.config.impl.AbstractConfigValue modified = modifier.modifyChildMayThrow(k, v); - if (modified != v) { - if (changes == null) - changes = new HashMap(); - changes.put(k, modified); - } - } - if (changes == null) { - return this; - } else { - Map modified = new HashMap(); - boolean sawUnresolved = false; - for (String k : keySet()) { - if (changes.containsKey(k)) { - com.gu.typesafe.config.impl.AbstractConfigValue newValue = changes.get(k); - if (newValue != null) { - modified.put(k, newValue); - if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) - sawUnresolved = true; - } else { - // remove this child; don't put it in the new map. - } - } else { - com.gu.typesafe.config.impl.AbstractConfigValue newValue = value.get(k); - modified.put(k, newValue); - if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) - sawUnresolved = true; - } - } - return new SimpleConfigObject(origin(), modified, - sawUnresolved ? ResolveStatus.UNRESOLVED : ResolveStatus.RESOLVED, - ignoresFallbacks()); - } - } - - private static final class ResolveModifier implements Modifier { - - final Path originalRestrict; - com.gu.typesafe.config.impl.ResolveContext context; - final com.gu.typesafe.config.impl.ResolveSource source; - - ResolveModifier(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) { - this.context = context; - this.source = source; - originalRestrict = context.restrictToChild(); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue modifyChildMayThrow(String key, com.gu.typesafe.config.impl.AbstractConfigValue v) throws NotPossibleToResolve { - if (context.isRestrictedToChild()) { - if (key.equals(context.restrictToChild().first())) { - Path remainder = context.restrictToChild().remainder(); - if (remainder != null) { - com.gu.typesafe.config.impl.ResolveResult result = context.restrict(remainder).resolve(v, - source); - context = result.context.unrestricted().restrict(originalRestrict); - return result.value; - } else { - // we don't want to resolve the leaf child. - return v; - } - } else { - // not in the restrictToChild path - return v; - } - } else { - // no restrictToChild, resolve everything - com.gu.typesafe.config.impl.ResolveResult result = context.unrestricted().resolve(v, source); - context = result.context.unrestricted().restrict(originalRestrict); - return result.value; - } - } - - } - - @Override - com.gu.typesafe.config.impl.ResolveResult resolveSubstitutions(com.gu.typesafe.config.impl.ResolveContext context, com.gu.typesafe.config.impl.ResolveSource source) - throws NotPossibleToResolve { - if (resolveStatus() == ResolveStatus.RESOLVED) - return com.gu.typesafe.config.impl.ResolveResult.make(context, this); - - final com.gu.typesafe.config.impl.ResolveSource sourceWithParent = source.pushParent(this); - - try { - ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); - - com.gu.typesafe.config.impl.AbstractConfigValue value = modifyMayThrow(modifier); - return com.gu.typesafe.config.impl.ResolveResult.make(modifier.context, value).asObjectResult(); - } catch (NotPossibleToResolve e) { - throw e; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new ConfigException.BugOrBroken("unexpected checked exception", e); - } - } - - @Override - SimpleConfigObject relativized(final Path prefix) { - return modify(new NoExceptionsModifier() { - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue modifyChild(String key, com.gu.typesafe.config.impl.AbstractConfigValue v) { - return v.relativized(prefix); - } - - }); - } - - // this is only Serializable to chill out a findbugs warning - static final private class RenderComparator implements java.util.Comparator, Serializable { - private static final long serialVersionUID = 1L; - - private static boolean isAllDigits(String s) { - int length = s.length(); - - // empty string doesn't count as a number - // string longer than "max number of digits in a long" cannot be parsed as a long - if (length == 0) - return false; - - for (int i = 0; i < length; ++i) { - char c = s.charAt(i); - - if (!Character.isDigit(c)) - return false; - } - return true; - } - - // This is supposed to sort numbers before strings, - // and sort the numbers numerically. The point is - // to make objects which are really list-like - // (numeric indices) appear in order. - @Override - public int compare(String a, String b) { - boolean aDigits = isAllDigits(a); - boolean bDigits = isAllDigits(b); - if (aDigits && bDigits) { - return new BigInteger(a).compareTo(new BigInteger(b)); - } else if (aDigits) { - return -1; - } else if (bDigits) { - return 1; - } else { - return a.compareTo(b); - } - } - } - - @Override - protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - if (isEmpty()) { - sb.append("{}"); - } else { - String[] keys = keySet().toArray(new String[size()]); - boolean singleton = keys.length == 1; - boolean shouldCompact = singleton && options.getCompactKeys(); - boolean outerBraces = options.getJson() || (!atRoot && !shouldCompact); - - int innerIndent; - if (outerBraces) { - innerIndent = indent + 1; - sb.append("{"); - - if (options.getFormatted()) - sb.append('\n'); - } else { - innerIndent = indent; - } - - int separatorCount = 0; - Arrays.sort(keys, new RenderComparator()); - for (String k : keys) { - com.gu.typesafe.config.impl.AbstractConfigValue v; - v = value.get(k); - - if (options.getOriginComments()) { - String[] lines = v.origin().description().split("\n"); - for (String l : lines) { - indent(sb, indent + 1, options); - sb.append('#'); - if (!l.isEmpty()) - sb.append(' '); - sb.append(l); - sb.append("\n"); - } - } - if (options.getComments()) { - for (String comment : v.origin().comments()) { - indent(sb, innerIndent, options); - sb.append("#"); - if (!comment.startsWith(" ")) - sb.append(' '); - sb.append(comment); - sb.append("\n"); - } - } - if (shouldCompact && !atRoot) { - sb.append("."); - } else { - indent(sb, innerIndent, options); - } - v.render(sb, innerIndent, false /* atRoot */, k, options); - - if (options.getFormatted()) { - if (options.getJson()) { - sb.append(","); - separatorCount = 2; - } else { - separatorCount = 1; - } - sb.append('\n'); - } else { - sb.append(","); - separatorCount = 1; - } - } - // chop last commas/newlines - sb.setLength(sb.length() - separatorCount); - - if (outerBraces) { - if (options.getFormatted()) { - sb.append('\n'); // put a newline back - if (outerBraces) - indent(sb, indent, options); - } - sb.append("}"); - } - } - if (atRoot && options.getFormatted()) - sb.append('\n'); - } - - @Override - public com.gu.typesafe.config.impl.AbstractConfigValue get(Object key) { - return value.get(key); - } - - private static boolean mapEquals(Map a, Map b) { - if (a == b) - return true; - - Set aKeys = a.keySet(); - Set bKeys = b.keySet(); - - if (!aKeys.equals(bKeys)) - return false; - - for (String key : aKeys) { - if (!a.get(key).equals(b.get(key))) - return false; - } - return true; - } - - private static int mapHash(Map m) { - // the keys have to be sorted, otherwise we could be equal - // to another map but have a different hashcode. - List keys = new ArrayList(); - keys.addAll(m.keySet()); - Collections.sort(keys); - - int valuesHash = 0; - for (String k : keys) { - valuesHash += m.get(k).hashCode(); - } - return 41 * (41 + keys.hashCode()) + valuesHash; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof com.gu.typesafe.config.ConfigObject; - } - - @Override - public boolean equals(Object other) { - // note that "origin" is deliberately NOT part of equality. - // neither are other "extras" like ignoresFallbacks or resolve status. - if (other instanceof com.gu.typesafe.config.ConfigObject) { - // optimization to avoid unwrapped() for two ConfigObject, - // which is what AbstractConfigValue does. - return canEqual(other) && mapEquals(this, ((ConfigObject) other)); - } else { - return false; - } - } - - @Override - public int hashCode() { - // note that "origin" is deliberately NOT part of equality - // neither are other "extras" like ignoresFallbacks or resolve status. - return mapHash(this); - } - - @Override - public boolean containsKey(Object key) { - return value.containsKey(key); - } - - @Override - public Set keySet() { - return value.keySet(); - } - - @Override - public boolean containsValue(Object v) { - return value.containsValue(v); - } - - @Override - public Set> entrySet() { - // total bloat just to work around lack of type variance - - HashSet> entries = new HashSet>(); - for (Map.Entry e : value.entrySet()) { - entries.add(new AbstractMap.SimpleImmutableEntry( - e.getKey(), e - .getValue())); - } - return entries; - } - - @Override - public boolean isEmpty() { - return value.isEmpty(); - } - - @Override - public int size() { - return value.size(); - } - - @Override - public Collection values() { - return new HashSet(value.values()); - } - - final private static String EMPTY_NAME = "empty config"; - final private static SimpleConfigObject emptyInstance = empty(com.gu.typesafe.config.impl.SimpleConfigOrigin - .newSimple(EMPTY_NAME)); - - final static SimpleConfigObject empty() { - return emptyInstance; - } - - final static SimpleConfigObject empty(com.gu.typesafe.config.ConfigOrigin origin) { - if (origin == null) - return empty(); - else - return new SimpleConfigObject(origin, - Collections. emptyMap()); - } - - final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { - return new SimpleConfigObject(com.gu.typesafe.config.impl.SimpleConfigOrigin.newSimple( - baseOrigin.description() + " (not found)"), - Collections. emptyMap()); - } - - // serialization all goes through SerializedConfigValue - private Object writeReplace() throws ObjectStreamException { - return new com.gu.typesafe.config.impl.SerializedConfigValue(this); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigOrigin.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigOrigin.java deleted file mode 100644 index ea2b4d4a7a0..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleConfigOrigin.java +++ /dev/null @@ -1,575 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.impl.SerializedConfigValue.SerializedField; - -// it would be cleaner to have a class hierarchy for various origin types, -// but was hoping this would be enough simpler to be a little messy. eh. -final class SimpleConfigOrigin implements com.gu.typesafe.config.ConfigOrigin { - - final private String description; - final private int lineNumber; - final private int endLineNumber; - final private OriginType originType; - final private String urlOrNull; - final private String resourceOrNull; - final private List commentsOrNull; - - protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, OriginType originType, - String urlOrNull, String resourceOrNull, List commentsOrNull) { - if (description == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("description may not be null"); - this.description = description; - this.lineNumber = lineNumber; - this.endLineNumber = endLineNumber; - this.originType = originType; - this.urlOrNull = urlOrNull; - this.resourceOrNull = resourceOrNull; - this.commentsOrNull = commentsOrNull; - } - - static SimpleConfigOrigin newSimple(String description) { - return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null, null); - } - - static SimpleConfigOrigin newFile(String filename) { - String url; - try { - url = (new File(filename)).toURI().toURL().toExternalForm(); - } catch (MalformedURLException e) { - url = null; - } - return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null, null); - } - - static SimpleConfigOrigin newURL(URL url) { - String u = url.toExternalForm(); - return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null, null); - } - - static SimpleConfigOrigin newResource(String resource, URL url) { - String desc; - if (url != null) - desc = resource + " @ " + url.toExternalForm(); - else - desc = resource; - return new SimpleConfigOrigin(desc, -1, -1, OriginType.RESOURCE, url != null ? url.toExternalForm() : null, - resource, null); - } - - static SimpleConfigOrigin newResource(String resource) { - return newResource(resource, null); - } - - @Override - public SimpleConfigOrigin withLineNumber(int lineNumber) { - if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { - return this; - } else { - return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, this.originType, this.urlOrNull, - this.resourceOrNull, this.commentsOrNull); - } - } - - SimpleConfigOrigin addURL(URL url) { - return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, - url != null ? url.toExternalForm() : null, this.resourceOrNull, this.commentsOrNull); - } - - @Override - public SimpleConfigOrigin withComments(List comments) { - if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) { - return this; - } else { - return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, - this.urlOrNull, this.resourceOrNull, comments); - } - } - - SimpleConfigOrigin prependComments(List comments) { - if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { - return this; - } else if (this.commentsOrNull == null) { - return withComments(comments); - } else { - List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); - merged.addAll(comments); - merged.addAll(this.commentsOrNull); - return withComments(merged); - } - } - - SimpleConfigOrigin appendComments(List comments) { - if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { - return this; - } else if (this.commentsOrNull == null) { - return withComments(comments); - } else { - List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); - merged.addAll(this.commentsOrNull); - merged.addAll(comments); - return withComments(merged); - } - } - - @Override - public String description() { - if (lineNumber < 0) { - return description; - } else if (endLineNumber == lineNumber) { - return description + ": " + lineNumber; - } else { - return description + ": " + lineNumber + "-" + endLineNumber; - } - } - - @Override - public boolean equals(Object other) { - if (other instanceof SimpleConfigOrigin) { - SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other; - - return this.description.equals(otherOrigin.description) && this.lineNumber == otherOrigin.lineNumber - && this.endLineNumber == otherOrigin.endLineNumber && this.originType == otherOrigin.originType - && ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull) - && ConfigImplUtil.equalsHandlingNull(this.resourceOrNull, otherOrigin.resourceOrNull); - } else { - return false; - } - } - - @Override - public int hashCode() { - int h = 41 * (41 + description.hashCode()); - h = 41 * (h + lineNumber); - h = 41 * (h + endLineNumber); - h = 41 * (h + originType.hashCode()); - if (urlOrNull != null) - h = 41 * (h + urlOrNull.hashCode()); - if (resourceOrNull != null) - h = 41 * (h + resourceOrNull.hashCode()); - return h; - } - - @Override - public String toString() { - return "ConfigOrigin(" + description + ")"; - } - - @Override - public String filename() { - if (originType == OriginType.FILE) { - return description; - } else if (urlOrNull != null) { - URL url; - try { - url = new URL(urlOrNull); - } catch (MalformedURLException e) { - return null; - } - if (url.getProtocol().equals("file")) { - return url.getFile(); - } else { - return null; - } - } else { - return null; - } - } - - @Override - public URL url() { - if (urlOrNull == null) { - return null; - } else { - try { - return new URL(urlOrNull); - } catch (MalformedURLException e) { - return null; - } - } - } - - @Override - public String resource() { - return resourceOrNull; - } - - @Override - public int lineNumber() { - return lineNumber; - } - - @Override - public List comments() { - if (commentsOrNull != null) { - return Collections.unmodifiableList(commentsOrNull); - } else { - return Collections.emptyList(); - } - } - - static final String MERGE_OF_PREFIX = "merge of "; - - private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) { - String mergedDesc; - int mergedStartLine; - int mergedEndLine; - List mergedComments; - - OriginType mergedType; - if (a.originType == b.originType) { - mergedType = a.originType; - } else { - mergedType = OriginType.GENERIC; - } - - // first use the "description" field which has no line numbers - // cluttering it. - String aDesc = a.description; - String bDesc = b.description; - if (aDesc.startsWith(MERGE_OF_PREFIX)) - aDesc = aDesc.substring(MERGE_OF_PREFIX.length()); - if (bDesc.startsWith(MERGE_OF_PREFIX)) - bDesc = bDesc.substring(MERGE_OF_PREFIX.length()); - - if (aDesc.equals(bDesc)) { - mergedDesc = aDesc; - - if (a.lineNumber < 0) - mergedStartLine = b.lineNumber; - else if (b.lineNumber < 0) - mergedStartLine = a.lineNumber; - else - mergedStartLine = Math.min(a.lineNumber, b.lineNumber); - - mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber); - } else { - // this whole merge song-and-dance was intended to avoid this case - // whenever possible, but we've lost. Now we have to lose some - // structured information and cram into a string. - - // description() method includes line numbers, so use it instead - // of description field. - String aFull = a.description(); - String bFull = b.description(); - if (aFull.startsWith(MERGE_OF_PREFIX)) - aFull = aFull.substring(MERGE_OF_PREFIX.length()); - if (bFull.startsWith(MERGE_OF_PREFIX)) - bFull = bFull.substring(MERGE_OF_PREFIX.length()); - - mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull; - - mergedStartLine = -1; - mergedEndLine = -1; - } - - String mergedURL; - if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) { - mergedURL = a.urlOrNull; - } else { - mergedURL = null; - } - - String mergedResource; - if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) { - mergedResource = a.resourceOrNull; - } else { - mergedResource = null; - } - - if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) { - mergedComments = a.commentsOrNull; - } else { - mergedComments = new ArrayList(); - if (a.commentsOrNull != null) - mergedComments.addAll(a.commentsOrNull); - if (b.commentsOrNull != null) - mergedComments.addAll(b.commentsOrNull); - } - - return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, mergedURL, - mergedResource, mergedComments); - } - - private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { - int count = 0; - - if (a.originType == b.originType) - count += 1; - - if (a.description.equals(b.description)) { - count += 1; - - // only count these if the description field (which is the file - // or resource name) also matches. - if (a.lineNumber == b.lineNumber) - count += 1; - if (a.endLineNumber == b.endLineNumber) - count += 1; - if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) - count += 1; - if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) - count += 1; - } - - return count; - } - - // this picks the best pair to merge, because the pair has the most in - // common. we want to merge two lines in the same file rather than something - // else with one of the lines; because two lines in the same file can be - // better consolidated. - private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, SimpleConfigOrigin c) { - if (similarity(a, b) >= similarity(b, c)) { - return mergeTwo(mergeTwo(a, b), c); - } else { - return mergeTwo(a, mergeTwo(b, c)); - } - } - - static com.gu.typesafe.config.ConfigOrigin mergeOrigins(com.gu.typesafe.config.ConfigOrigin a, com.gu.typesafe.config.ConfigOrigin b) { - return mergeTwo((SimpleConfigOrigin) a, (SimpleConfigOrigin) b); - } - - static com.gu.typesafe.config.ConfigOrigin mergeOrigins(List stack) { - List origins = new ArrayList(stack.size()); - for (com.gu.typesafe.config.impl.AbstractConfigValue v : stack) { - origins.add(v.origin()); - } - return mergeOrigins(origins); - } - - static com.gu.typesafe.config.ConfigOrigin mergeOrigins(Collection stack) { - if (stack.isEmpty()) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("can't merge empty list of origins"); - } else if (stack.size() == 1) { - return stack.iterator().next(); - } else if (stack.size() == 2) { - Iterator i = stack.iterator(); - return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next()); - } else { - List remaining = new ArrayList(stack.size()); - for (ConfigOrigin o : stack) { - remaining.add((SimpleConfigOrigin) o); - } - while (remaining.size() > 2) { - SimpleConfigOrigin c = remaining.get(remaining.size() - 1); - remaining.remove(remaining.size() - 1); - SimpleConfigOrigin b = remaining.get(remaining.size() - 1); - remaining.remove(remaining.size() - 1); - SimpleConfigOrigin a = remaining.get(remaining.size() - 1); - remaining.remove(remaining.size() - 1); - - SimpleConfigOrigin merged = mergeThree(a, b, c); - - remaining.add(merged); - } - - // should be down to either 1 or 2 - return mergeOrigins(remaining); - } - } - - Map toFields() { - Map m = new EnumMap(SerializedField.class); - - m.put(SerializedField.ORIGIN_DESCRIPTION, description); - - if (lineNumber >= 0) - m.put(SerializedField.ORIGIN_LINE_NUMBER, lineNumber); - if (endLineNumber >= 0) - m.put(SerializedField.ORIGIN_END_LINE_NUMBER, endLineNumber); - - m.put(SerializedField.ORIGIN_TYPE, originType.ordinal()); - - if (urlOrNull != null) - m.put(SerializedField.ORIGIN_URL, urlOrNull); - if (resourceOrNull != null) - m.put(SerializedField.ORIGIN_RESOURCE, resourceOrNull); - if (commentsOrNull != null) - m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull); - - return m; - } - - Map toFieldsDelta(SimpleConfigOrigin baseOrigin) { - Map baseFields; - if (baseOrigin != null) - baseFields = baseOrigin.toFields(); - else - baseFields = Collections. emptyMap(); - return fieldsDelta(baseFields, toFields()); - } - - // Here we're trying to avoid serializing the same info over and over - // in the common case that child objects have the same origin fields - // as their parent objects. e.g. we don't need to store the source - // filename with every single value. - static Map fieldsDelta(Map base, - Map child) { - Map m = new EnumMap(child); - - for (Map.Entry baseEntry : base.entrySet()) { - SerializedField f = baseEntry.getKey(); - if (m.containsKey(f) && ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) { - // if field is unchanged, just remove it so we inherit - m.remove(f); - } else if (!m.containsKey(f)) { - // if field has been removed, we have to add a deletion entry - switch (f) { - case ORIGIN_DESCRIPTION: - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("origin missing description field? " + child); - case ORIGIN_LINE_NUMBER: - m.put(SerializedField.ORIGIN_LINE_NUMBER, -1); - break; - case ORIGIN_END_LINE_NUMBER: - m.put(SerializedField.ORIGIN_END_LINE_NUMBER, -1); - break; - case ORIGIN_TYPE: - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("should always be an ORIGIN_TYPE field"); - case ORIGIN_URL: - m.put(SerializedField.ORIGIN_NULL_URL, ""); - break; - case ORIGIN_RESOURCE: - m.put(SerializedField.ORIGIN_NULL_RESOURCE, ""); - break; - case ORIGIN_COMMENTS: - m.put(SerializedField.ORIGIN_NULL_COMMENTS, ""); - break; - case ORIGIN_NULL_URL: // FALL THRU - case ORIGIN_NULL_RESOURCE: // FALL THRU - case ORIGIN_NULL_COMMENTS: - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("computing delta, base object should not contain " + f + " " - + base); - case END_MARKER: - case ROOT_VALUE: - case ROOT_WAS_CONFIG: - case UNKNOWN: - case VALUE_DATA: - case VALUE_ORIGIN: - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("should not appear here: " + f); - } - } else { - // field is in base and child, but differs, so leave it - } - } - - return m; - } - - static SimpleConfigOrigin fromFields(Map m) throws IOException { - // we represent a null origin as one with no fields at all - if (m.isEmpty()) - return null; - - String description = (String) m.get(SerializedField.ORIGIN_DESCRIPTION); - Integer lineNumber = (Integer) m.get(SerializedField.ORIGIN_LINE_NUMBER); - Integer endLineNumber = (Integer) m.get(SerializedField.ORIGIN_END_LINE_NUMBER); - Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE); - if (originTypeOrdinal == null) - throw new IOException("Missing ORIGIN_TYPE field"); - OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; - String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); - String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE); - @SuppressWarnings("unchecked") - List commentsOrNull = (List) m.get(SerializedField.ORIGIN_COMMENTS); - // Older versions did not have a resource field, they stuffed it into - // the description. - if (originType == OriginType.RESOURCE && resourceOrNull == null) { - resourceOrNull = description; - } - return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1, - endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, resourceOrNull, commentsOrNull); - } - - static Map applyFieldsDelta(Map base, - Map delta) throws IOException { - - Map m = new EnumMap(delta); - - for (Map.Entry baseEntry : base.entrySet()) { - SerializedField f = baseEntry.getKey(); - if (delta.containsKey(f)) { - // delta overrides when keys are in both - // "m" should already contain the right thing - } else { - // base has the key and delta does not. - // we inherit from base unless a "NULL" key blocks. - switch (f) { - case ORIGIN_DESCRIPTION: - m.put(f, base.get(f)); - break; - case ORIGIN_URL: - if (delta.containsKey(SerializedField.ORIGIN_NULL_URL)) { - m.remove(SerializedField.ORIGIN_NULL_URL); - } else { - m.put(f, base.get(f)); - } - break; - case ORIGIN_RESOURCE: - if (delta.containsKey(SerializedField.ORIGIN_NULL_RESOURCE)) { - m.remove(SerializedField.ORIGIN_NULL_RESOURCE); - } else { - m.put(f, base.get(f)); - } - break; - case ORIGIN_COMMENTS: - if (delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS)) { - m.remove(SerializedField.ORIGIN_NULL_COMMENTS); - } else { - m.put(f, base.get(f)); - } - break; - case ORIGIN_NULL_URL: // FALL THRU - case ORIGIN_NULL_RESOURCE: // FALL THRU - case ORIGIN_NULL_COMMENTS: // FALL THRU - // base objects shouldn't contain these, should just - // lack the field. these are only in deltas. - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("applying fields, base object should not contain " + f + " " - + base); - case ORIGIN_END_LINE_NUMBER: // FALL THRU - case ORIGIN_LINE_NUMBER: // FALL THRU - case ORIGIN_TYPE: - m.put(f, base.get(f)); - break; - - case END_MARKER: - case ROOT_VALUE: - case ROOT_WAS_CONFIG: - case UNKNOWN: - case VALUE_DATA: - case VALUE_ORIGIN: - throw new ConfigException.BugOrBroken("should not appear here: " + f); - } - } - } - return m; - } - - static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin, Map delta) - throws IOException { - Map baseFields; - if (baseOrigin != null) - baseFields = baseOrigin.toFields(); - else - baseFields = Collections. emptyMap(); - Map fields = applyFieldsDelta(baseFields, delta); - return fromFields(fields); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncludeContext.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncludeContext.java deleted file mode 100644 index 646f6a0d163..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncludeContext.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigIncludeContext; -import com.gu.typesafe.config.ConfigParseOptions; -import com.gu.typesafe.config.ConfigParseable; - -class SimpleIncludeContext implements com.gu.typesafe.config.ConfigIncludeContext { - - private final com.gu.typesafe.config.impl.Parseable parseable; - private final com.gu.typesafe.config.ConfigParseOptions options; - - SimpleIncludeContext(com.gu.typesafe.config.impl.Parseable parseable) { - this.parseable = parseable; - this.options = SimpleIncluder.clearForInclude(parseable.options()); - } - - private SimpleIncludeContext(com.gu.typesafe.config.impl.Parseable parseable, com.gu.typesafe.config.ConfigParseOptions options) { - this.parseable = parseable; - this.options = options; - } - - SimpleIncludeContext withParseable(Parseable parseable) { - if (parseable == this.parseable) - return this; - else - return new SimpleIncludeContext(parseable); - } - - @Override - public ConfigParseable relativeTo(String filename) { - if (ConfigImpl.traceLoadsEnabled()) - ConfigImpl.trace("Looking for '" + filename + "' relative to " + parseable); - if (parseable != null) - return parseable.relativeTo(filename); - else - return null; - } - - @Override - public com.gu.typesafe.config.ConfigParseOptions parseOptions() { - return options; - } - - @Override - public ConfigIncludeContext setParseOptions(ConfigParseOptions options) { - return new SimpleIncludeContext(parseable, options.setSyntax(null).setOriginDescription(null)); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncluder.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncluder.java deleted file mode 100644 index 15621b02bc8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SimpleIncluder.java +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import com.gu.typesafe.config.*; -import com.gu.typesafe.config.ConfigIncluderClasspath; - -class SimpleIncluder implements com.gu.typesafe.config.impl.FullIncluder { - - private com.gu.typesafe.config.ConfigIncluder fallback; - - SimpleIncluder(com.gu.typesafe.config.ConfigIncluder fallback) { - this.fallback = fallback; - } - - // ConfigIncludeContext does this for us on its options - static com.gu.typesafe.config.ConfigParseOptions clearForInclude(com.gu.typesafe.config.ConfigParseOptions options) { - // the class loader and includer are inherited, but not this other - // stuff. - return options.setSyntax(null).setOriginDescription(null).setAllowMissing(true); - } - - // this is the heuristic includer - @Override - public com.gu.typesafe.config.ConfigObject include(final com.gu.typesafe.config.ConfigIncludeContext context, String name) { - com.gu.typesafe.config.ConfigObject obj = includeWithoutFallback(context, name); - - // now use the fallback includer if any and merge - // its result. - if (fallback != null) { - return obj.withFallback(fallback.include(context, name)); - } else { - return obj; - } - } - - // the heuristic includer in static form - static com.gu.typesafe.config.ConfigObject includeWithoutFallback(final com.gu.typesafe.config.ConfigIncludeContext context, String name) { - // the heuristic is valid URL then URL, else relative to including file; - // relativeTo in a file falls back to classpath inside relativeTo(). - - URL url; - try { - url = new URL(name); - } catch (MalformedURLException e) { - url = null; - } - - if (url != null) { - return includeURLWithoutFallback(context, url); - } else { - NameSource source = new RelativeNameSource(context); - return fromBasename(source, name, context.parseOptions()); - } - } - - @Override - public com.gu.typesafe.config.ConfigObject includeURL(com.gu.typesafe.config.ConfigIncludeContext context, URL url) { - com.gu.typesafe.config.ConfigObject obj = includeURLWithoutFallback(context, url); - - // now use the fallback includer if any and merge - // its result. - if (fallback != null && fallback instanceof com.gu.typesafe.config.ConfigIncluderURL) { - return obj.withFallback(((com.gu.typesafe.config.ConfigIncluderURL) fallback).includeURL(context, url)); - } else { - return obj; - } - } - - static com.gu.typesafe.config.ConfigObject includeURLWithoutFallback(final com.gu.typesafe.config.ConfigIncludeContext context, URL url) { - return com.gu.typesafe.config.ConfigFactory.parseURL(url, context.parseOptions()).root(); - } - - @Override - public com.gu.typesafe.config.ConfigObject includeFile(com.gu.typesafe.config.ConfigIncludeContext context, File file) { - com.gu.typesafe.config.ConfigObject obj = includeFileWithoutFallback(context, file); - - // now use the fallback includer if any and merge - // its result. - if (fallback != null && fallback instanceof com.gu.typesafe.config.ConfigIncluderFile) { - return obj.withFallback(((com.gu.typesafe.config.ConfigIncluderFile) fallback).includeFile(context, file)); - } else { - return obj; - } - } - - static com.gu.typesafe.config.ConfigObject includeFileWithoutFallback(final com.gu.typesafe.config.ConfigIncludeContext context, File file) { - return com.gu.typesafe.config.ConfigFactory.parseFileAnySyntax(file, context.parseOptions()).root(); - } - - @Override - public com.gu.typesafe.config.ConfigObject includeResources(com.gu.typesafe.config.ConfigIncludeContext context, String resource) { - com.gu.typesafe.config.ConfigObject obj = includeResourceWithoutFallback(context, resource); - - // now use the fallback includer if any and merge - // its result. - if (fallback != null && fallback instanceof ConfigIncluderClasspath) { - return obj.withFallback(((ConfigIncluderClasspath) fallback).includeResources(context, - resource)); - } else { - return obj; - } - } - - static com.gu.typesafe.config.ConfigObject includeResourceWithoutFallback(final com.gu.typesafe.config.ConfigIncludeContext context, - String resource) { - return ConfigFactory.parseResourcesAnySyntax(resource, context.parseOptions()).root(); - } - - @Override - public com.gu.typesafe.config.ConfigIncluder withFallback(com.gu.typesafe.config.ConfigIncluder fallback) { - if (this == fallback) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("trying to create includer cycle"); - } else if (this.fallback == fallback) { - return this; - } else if (this.fallback != null) { - return new SimpleIncluder(this.fallback.withFallback(fallback)); - } else { - return new SimpleIncluder(fallback); - } - } - - interface NameSource { - com.gu.typesafe.config.ConfigParseable nameToParseable(String name, com.gu.typesafe.config.ConfigParseOptions parseOptions); - } - - static private class RelativeNameSource implements NameSource { - final private com.gu.typesafe.config.ConfigIncludeContext context; - - RelativeNameSource(com.gu.typesafe.config.ConfigIncludeContext context) { - this.context = context; - } - - @Override - public com.gu.typesafe.config.ConfigParseable nameToParseable(String name, com.gu.typesafe.config.ConfigParseOptions options) { - com.gu.typesafe.config.ConfigParseable p = context.relativeTo(name); - if (p == null) { - // avoid returning null - return Parseable - .newNotFound(name, "include was not found: '" + name + "'", options); - } else { - return p; - } - } - }; - - // this function is a little tricky because there are three places we're - // trying to use it; for 'include "basename"' in a .conf file, for - // loading app.{conf,json,properties} from classpath, and for - // loading app.{conf,json,properties} from the filesystem. - static com.gu.typesafe.config.ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) { - com.gu.typesafe.config.ConfigObject obj; - if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) { - com.gu.typesafe.config.ConfigParseable p = source.nameToParseable(name, options); - - obj = p.parse(p.options().setAllowMissing(options.getAllowMissing())); - } else { - com.gu.typesafe.config.ConfigParseable confHandle = source.nameToParseable(name + ".conf", options); - com.gu.typesafe.config.ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options); - ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options); - boolean gotSomething = false; - List fails = new ArrayList(); - - com.gu.typesafe.config.ConfigSyntax syntax = options.getSyntax(); - - obj = com.gu.typesafe.config.impl.SimpleConfigObject.empty(com.gu.typesafe.config.impl.SimpleConfigOrigin.newSimple(name)); - if (syntax == null || syntax == com.gu.typesafe.config.ConfigSyntax.CONF) { - try { - obj = confHandle.parse(confHandle.options().setAllowMissing(false) - .setSyntax(com.gu.typesafe.config.ConfigSyntax.CONF)); - gotSomething = true; - } catch (com.gu.typesafe.config.ConfigException.IO e) { - fails.add(e); - } - } - - if (syntax == null || syntax == com.gu.typesafe.config.ConfigSyntax.JSON) { - try { - com.gu.typesafe.config.ConfigObject parsed = jsonHandle.parse(jsonHandle.options() - .setAllowMissing(false).setSyntax(com.gu.typesafe.config.ConfigSyntax.JSON)); - obj = obj.withFallback(parsed); - gotSomething = true; - } catch (com.gu.typesafe.config.ConfigException.IO e) { - fails.add(e); - } - } - - if (syntax == null || syntax == com.gu.typesafe.config.ConfigSyntax.PROPERTIES) { - try { - com.gu.typesafe.config.ConfigObject parsed = propsHandle.parse(propsHandle.options() - .setAllowMissing(false).setSyntax(ConfigSyntax.PROPERTIES)); - obj = obj.withFallback(parsed); - gotSomething = true; - } catch (com.gu.typesafe.config.ConfigException.IO e) { - fails.add(e); - } - } - - if (!options.getAllowMissing() && !gotSomething) { - if (com.gu.typesafe.config.impl.ConfigImpl.traceLoadsEnabled()) { - // the individual exceptions should have been logged already - // with tracing enabled - com.gu.typesafe.config.impl.ConfigImpl.trace("Did not find '" + name - + "' with any extension (.conf, .json, .properties); " - + "exceptions should have been logged above."); - } - - if (fails.isEmpty()) { - // this should not happen - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "should not be reached: nothing found but no exceptions thrown"); - } else { - StringBuilder sb = new StringBuilder(); - for (Throwable t : fails) { - sb.append(t.getMessage()); - sb.append(", "); - } - sb.setLength(sb.length() - 2); - throw new ConfigException.IO(com.gu.typesafe.config.impl.SimpleConfigOrigin.newSimple(name), sb.toString(), - fails.get(0)); - } - } else if (!gotSomething) { - if (com.gu.typesafe.config.impl.ConfigImpl.traceLoadsEnabled()) { - ConfigImpl.trace("Did not find '" + name - + "' with any extension (.conf, .json, .properties); but '" + name - + "' is allowed to be missing. Exceptions from load attempts should have been logged above."); - } - } - } - - return obj; - } - - // the Proxy is a proxy for an application-provided includer that uses our - // default implementations when the application-provided includer doesn't - // have an implementation. - static private class Proxy implements com.gu.typesafe.config.impl.FullIncluder { - final com.gu.typesafe.config.ConfigIncluder delegate; - - Proxy(com.gu.typesafe.config.ConfigIncluder delegate) { - this.delegate = delegate; - } - - @Override - public com.gu.typesafe.config.ConfigIncluder withFallback(com.gu.typesafe.config.ConfigIncluder fallback) { - // we never fall back - return this; - } - - @Override - public com.gu.typesafe.config.ConfigObject include(com.gu.typesafe.config.ConfigIncludeContext context, String what) { - return delegate.include(context, what); - } - - @Override - public com.gu.typesafe.config.ConfigObject includeResources(com.gu.typesafe.config.ConfigIncludeContext context, String what) { - if (delegate instanceof ConfigIncluderClasspath) - return ((ConfigIncluderClasspath) delegate).includeResources(context, what); - else - return includeResourceWithoutFallback(context, what); - } - - @Override - public com.gu.typesafe.config.ConfigObject includeURL(com.gu.typesafe.config.ConfigIncludeContext context, URL what) { - if (delegate instanceof com.gu.typesafe.config.ConfigIncluderURL) - return ((ConfigIncluderURL) delegate).includeURL(context, what); - else - return includeURLWithoutFallback(context, what); - } - - @Override - public ConfigObject includeFile(ConfigIncludeContext context, File what) { - if (delegate instanceof com.gu.typesafe.config.ConfigIncluderFile) - return ((ConfigIncluderFile) delegate).includeFile(context, what); - else - return includeFileWithoutFallback(context, what); - } - } - - static com.gu.typesafe.config.impl.FullIncluder makeFull(ConfigIncluder includer) { - if (includer instanceof com.gu.typesafe.config.impl.FullIncluder) - return (com.gu.typesafe.config.impl.FullIncluder) includer; - else - return new Proxy(includer); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/SubstitutionExpression.java b/scripts/src/main/java/com/gu/typesafe/config/impl/SubstitutionExpression.java deleted file mode 100644 index 873ecf81b24..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/SubstitutionExpression.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.gu.typesafe.config.impl; - -final class SubstitutionExpression { - - final private Path path; - final private boolean optional; - - SubstitutionExpression(Path path, boolean optional) { - this.path = path; - this.optional = optional; - } - - Path path() { - return path; - } - - boolean optional() { - return optional; - } - - SubstitutionExpression changePath(Path newPath) { - if (newPath == path) - return this; - else - return new SubstitutionExpression(newPath, optional); - } - - @Override - public String toString() { - return "${" + (optional ? "?" : "") + path.render() + "}"; - } - - @Override - public boolean equals(Object other) { - if (other instanceof SubstitutionExpression) { - SubstitutionExpression otherExp = (SubstitutionExpression) other; - return otherExp.path.equals(this.path) && otherExp.optional == this.optional; - } else { - return false; - } - } - - @Override - public int hashCode() { - int h = 41 * (41 + path.hashCode()); - h = 41 * (h + (optional ? 1 : 0)); - return h; - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Token.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Token.java deleted file mode 100644 index 071356d1093..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Token.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; - -class Token { - final private com.gu.typesafe.config.impl.TokenType tokenType; - final private String debugString; - final private com.gu.typesafe.config.ConfigOrigin origin; - final private String tokenText; - - Token(com.gu.typesafe.config.impl.TokenType tokenType, com.gu.typesafe.config.ConfigOrigin origin) { - this(tokenType, origin, null); - } - - Token(com.gu.typesafe.config.impl.TokenType tokenType, com.gu.typesafe.config.ConfigOrigin origin, String tokenText) { - this(tokenType, origin, tokenText, null); - } - - Token(com.gu.typesafe.config.impl.TokenType tokenType, com.gu.typesafe.config.ConfigOrigin origin, String tokenText, String debugString) { - this.tokenType = tokenType; - this.origin = origin; - this.debugString = debugString; - this.tokenText = tokenText; - } - - // this is used for singleton tokens like COMMA or OPEN_CURLY - static Token newWithoutOrigin(com.gu.typesafe.config.impl.TokenType tokenType, String debugString, String tokenText) { - return new Token(tokenType, null, tokenText, debugString); - } - - final com.gu.typesafe.config.impl.TokenType tokenType() { - return tokenType; - } - - public String tokenText() { return tokenText; } - - // this is final because we don't always use the origin() accessor, - // and we don't because it throws if origin is null - final ConfigOrigin origin() { - // code is only supposed to call origin() on token types that are - // expected to have an origin. - if (origin == null) - throw new ConfigException.BugOrBroken( - "tried to get origin from token that doesn't have one: " + this); - return origin; - } - - final int lineNumber() { - if (origin != null) - return origin.lineNumber(); - else - return -1; - } - - @Override - public String toString() { - if (debugString != null) - return debugString; - else - return tokenType.name(); - } - - protected boolean canEqual(Object other) { - return other instanceof Token; - } - - @Override - public boolean equals(Object other) { - if (other instanceof Token) { - // origin is deliberately left out - return canEqual(other) - && this.tokenType == ((Token) other).tokenType; - } else { - return false; - } - } - - @Override - public int hashCode() { - // origin is deliberately left out - return tokenType.hashCode(); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/TokenType.java b/scripts/src/main/java/com/gu/typesafe/config/impl/TokenType.java deleted file mode 100644 index a8261fb4ee8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/TokenType.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -enum TokenType { - START, - END, - COMMA, - EQUALS, - COLON, - OPEN_CURLY, - CLOSE_CURLY, - OPEN_SQUARE, - CLOSE_SQUARE, - VALUE, - NEWLINE, - UNQUOTED_TEXT, - IGNORED_WHITESPACE, - SUBSTITUTION, - PROBLEM, - COMMENT, - PLUS_EQUALS; -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Tokenizer.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Tokenizer.java deleted file mode 100644 index 1dea55ebd66..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Tokenizer.java +++ /dev/null @@ -1,695 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigSyntax; - -final class Tokenizer { - // this exception should not leave this file - private static class ProblemException extends Exception { - private static final long serialVersionUID = 1L; - - final private com.gu.typesafe.config.impl.Token problem; - - ProblemException(com.gu.typesafe.config.impl.Token problem) { - this.problem = problem; - } - - com.gu.typesafe.config.impl.Token problem() { - return problem; - } - } - - private static String asString(int codepoint) { - if (codepoint == '\n') - return "newline"; - else if (codepoint == '\t') - return "tab"; - else if (codepoint == -1) - return "end of file"; - else if (com.gu.typesafe.config.impl.ConfigImplUtil.isC0Control(codepoint)) - return String.format("control character 0x%x", codepoint); - else - return String.format("%c", codepoint); - } - - /** - * Tokenizes a Reader. Does not close the reader; you have to arrange to do - * that after you're done with the returned iterator. - */ - static Iterator tokenize(com.gu.typesafe.config.ConfigOrigin origin, Reader input, com.gu.typesafe.config.ConfigSyntax flavor) { - return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON); - } - - static String render(Iterator tokens) { - StringBuilder renderedText = new StringBuilder(); - while (tokens.hasNext()) { - renderedText.append(tokens.next().tokenText()); - } - return renderedText.toString(); - } - - private static class TokenIterator implements Iterator { - - private static class WhitespaceSaver { - // has to be saved inside value concatenations - private StringBuilder whitespace; - // may need to value-concat with next value - private boolean lastTokenWasSimpleValue; - - WhitespaceSaver() { - whitespace = new StringBuilder(); - lastTokenWasSimpleValue = false; - } - - void add(int c) { - whitespace.appendCodePoint(c); - } - - com.gu.typesafe.config.impl.Token check(com.gu.typesafe.config.impl.Token t, com.gu.typesafe.config.ConfigOrigin baseOrigin, int lineNumber) { - if (isSimpleValue(t)) { - return nextIsASimpleValue(baseOrigin, lineNumber); - } else { - return nextIsNotASimpleValue(baseOrigin, lineNumber); - } - } - - // called if the next token is not a simple value; - // discards any whitespace we were saving between - // simple values. - private com.gu.typesafe.config.impl.Token nextIsNotASimpleValue(com.gu.typesafe.config.ConfigOrigin baseOrigin, int lineNumber) { - lastTokenWasSimpleValue = false; - return createWhitespaceTokenFromSaver(baseOrigin, lineNumber); - } - - // called if the next token IS a simple value, - // so creates a whitespace token if the previous - // token also was. - private com.gu.typesafe.config.impl.Token nextIsASimpleValue(com.gu.typesafe.config.ConfigOrigin baseOrigin, - int lineNumber) { - com.gu.typesafe.config.impl.Token t = createWhitespaceTokenFromSaver(baseOrigin, lineNumber); - if (!lastTokenWasSimpleValue) { - lastTokenWasSimpleValue = true; - } - return t; - } - - private com.gu.typesafe.config.impl.Token createWhitespaceTokenFromSaver(com.gu.typesafe.config.ConfigOrigin baseOrigin, - int lineNumber) { - if (whitespace.length() > 0) { - com.gu.typesafe.config.impl.Token t; - if (lastTokenWasSimpleValue) { - t = com.gu.typesafe.config.impl.Tokens.newUnquotedText( - lineOrigin(baseOrigin, lineNumber), - whitespace.toString()); - } else { - t = com.gu.typesafe.config.impl.Tokens.newIgnoredWhitespace(lineOrigin(baseOrigin, lineNumber), - whitespace.toString()); - } - whitespace.setLength(0); // reset - return t; - } - return null; - } - } - - final private com.gu.typesafe.config.impl.SimpleConfigOrigin origin; - final private Reader input; - final private LinkedList buffer; - private int lineNumber; - private com.gu.typesafe.config.ConfigOrigin lineOrigin; - final private Queue tokens; - final private WhitespaceSaver whitespaceSaver; - final private boolean allowComments; - - TokenIterator(com.gu.typesafe.config.ConfigOrigin origin, Reader input, boolean allowComments) { - this.origin = (com.gu.typesafe.config.impl.SimpleConfigOrigin) origin; - this.input = input; - this.allowComments = allowComments; - this.buffer = new LinkedList(); - lineNumber = 1; - lineOrigin = this.origin.withLineNumber(lineNumber); - tokens = new LinkedList(); - tokens.add(com.gu.typesafe.config.impl.Tokens.START); - whitespaceSaver = new WhitespaceSaver(); - } - - - // this should ONLY be called from nextCharSkippingComments - // or when inside a quoted string, or when parsing a sequence - // like ${ or +=, everything else should use - // nextCharSkippingComments(). - private int nextCharRaw() { - if (buffer.isEmpty()) { - try { - return input.read(); - } catch (IOException e) { - throw new com.gu.typesafe.config.ConfigException.IO(origin, "read error: " - + e.getMessage(), e); - } - } else { - int c = buffer.pop(); - return c; - } - } - - private void putBack(int c) { - if (buffer.size() > 2) { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "bug: putBack() three times, undesirable look-ahead"); - } - buffer.push(c); - } - - static boolean isWhitespace(int c) { - return com.gu.typesafe.config.impl.ConfigImplUtil.isWhitespace(c); - } - - static boolean isWhitespaceNotNewline(int c) { - return c != '\n' && com.gu.typesafe.config.impl.ConfigImplUtil.isWhitespace(c); - } - - private boolean startOfComment(int c) { - if (c == -1) { - return false; - } else { - if (allowComments) { - if (c == '#') { - return true; - } else if (c == '/') { - int maybeSecondSlash = nextCharRaw(); - // we want to predictably NOT consume any chars - putBack(maybeSecondSlash); - if (maybeSecondSlash == '/') { - return true; - } else { - return false; - } - } else { - return false; - } - } else { - return false; - } - } - } - - // get next char, skipping non-newline whitespace - private int nextCharAfterWhitespace(WhitespaceSaver saver) { - for (;;) { - int c = nextCharRaw(); - - if (c == -1) { - return -1; - } else { - if (isWhitespaceNotNewline(c)) { - saver.add(c); - continue; - } else { - return c; - } - } - } - } - - private ProblemException problem(String message) { - return problem("", message, null); - } - - private ProblemException problem(String what, String message) { - return problem(what, message, null); - } - - private ProblemException problem(String what, String message, boolean suggestQuotes) { - return problem(what, message, suggestQuotes, null); - } - - private ProblemException problem(String what, String message, Throwable cause) { - return problem(lineOrigin, what, message, cause); - } - - private ProblemException problem(String what, String message, boolean suggestQuotes, - Throwable cause) { - return problem(lineOrigin, what, message, suggestQuotes, cause); - } - - private static ProblemException problem(com.gu.typesafe.config.ConfigOrigin origin, String what, - String message, - Throwable cause) { - return problem(origin, what, message, false, cause); - } - - private static ProblemException problem(com.gu.typesafe.config.ConfigOrigin origin, String what, String message, - boolean suggestQuotes, Throwable cause) { - if (what == null || message == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "internal error, creating bad ProblemException"); - return new ProblemException(com.gu.typesafe.config.impl.Tokens.newProblem(origin, what, message, suggestQuotes, - cause)); - } - - private static ProblemException problem(com.gu.typesafe.config.ConfigOrigin origin, String message) { - return problem(origin, "", message, null); - } - - private static com.gu.typesafe.config.ConfigOrigin lineOrigin(com.gu.typesafe.config.ConfigOrigin baseOrigin, - int lineNumber) { - return ((com.gu.typesafe.config.impl.SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); - } - - // ONE char has always been consumed, either the # or the first /, but - // not both slashes - private com.gu.typesafe.config.impl.Token pullComment(int firstChar) { - boolean doubleSlash = false; - if (firstChar == '/') { - int discard = nextCharRaw(); - if (discard != '/') - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("called pullComment but // not seen"); - doubleSlash = true; - } - - StringBuilder sb = new StringBuilder(); - for (;;) { - int c = nextCharRaw(); - if (c == -1 || c == '\n') { - putBack(c); - if (doubleSlash) - return com.gu.typesafe.config.impl.Tokens.newCommentDoubleSlash(lineOrigin, sb.toString()); - else - return com.gu.typesafe.config.impl.Tokens.newCommentHash(lineOrigin, sb.toString()); - } else { - sb.appendCodePoint(c); - } - } - } - - // chars JSON allows a number to start with - static final String firstNumberChars = "0123456789-"; - // chars JSON allows to be part of a number - static final String numberChars = "0123456789eE+-."; - // chars that stop an unquoted string - static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\"; - - // The rules here are intended to maximize convenience while - // avoiding confusion with real valid JSON. Basically anything - // that parses as JSON is treated the JSON way and otherwise - // we assume it's a string and let the parser sort it out. - private com.gu.typesafe.config.impl.Token pullUnquotedText() { - com.gu.typesafe.config.ConfigOrigin origin = lineOrigin; - StringBuilder sb = new StringBuilder(); - int c = nextCharRaw(); - while (true) { - if (c == -1) { - break; - } else if (notInUnquotedText.indexOf(c) >= 0) { - break; - } else if (isWhitespace(c)) { - break; - } else if (startOfComment(c)) { - break; - } else { - sb.appendCodePoint(c); - } - - // we parse true/false/null tokens as such no matter - // what is after them, as long as they are at the - // start of the unquoted token. - if (sb.length() == 4) { - String s = sb.toString(); - if (s.equals("true")) - return com.gu.typesafe.config.impl.Tokens.newBoolean(origin, true); - else if (s.equals("null")) - return com.gu.typesafe.config.impl.Tokens.newNull(origin); - } else if (sb.length() == 5) { - String s = sb.toString(); - if (s.equals("false")) - return com.gu.typesafe.config.impl.Tokens.newBoolean(origin, false); - } - - c = nextCharRaw(); - } - - // put back the char that ended the unquoted text - putBack(c); - - String s = sb.toString(); - return com.gu.typesafe.config.impl.Tokens.newUnquotedText(origin, s); - } - - private com.gu.typesafe.config.impl.Token pullNumber(int firstChar) throws ProblemException { - StringBuilder sb = new StringBuilder(); - sb.appendCodePoint(firstChar); - boolean containedDecimalOrE = false; - int c = nextCharRaw(); - while (c != -1 && numberChars.indexOf(c) >= 0) { - if (c == '.' || c == 'e' || c == 'E') - containedDecimalOrE = true; - sb.appendCodePoint(c); - c = nextCharRaw(); - } - // the last character we looked at wasn't part of the number, put it - // back - putBack(c); - String s = sb.toString(); - try { - if (containedDecimalOrE) { - // force floating point representation - return com.gu.typesafe.config.impl.Tokens.newDouble(lineOrigin, Double.parseDouble(s), s); - } else { - // this should throw if the integer is too large for Long - return com.gu.typesafe.config.impl.Tokens.newLong(lineOrigin, Long.parseLong(s), s); - } - } catch (NumberFormatException e) { - // not a number after all, see if it's an unquoted string. - for (char u : s.toCharArray()) { - if (notInUnquotedText.indexOf(u) >= 0) - throw problem(asString(u), "Reserved character '" + asString(u) - + "' is not allowed outside quotes", true /* suggestQuotes */); - } - // no evil chars so we just decide this was a string and - // not a number. - return com.gu.typesafe.config.impl.Tokens.newUnquotedText(lineOrigin, s); - } - } - - private void pullEscapeSequence(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { - int escaped = nextCharRaw(); - if (escaped == -1) - throw problem("End of input but backslash in string had nothing after it"); - - // This is needed so we return the unescaped escape characters back out when rendering - // the token - sbOrig.appendCodePoint('\\'); - sbOrig.appendCodePoint(escaped); - - switch (escaped) { - case '"': - sb.append('"'); - break; - case '\\': - sb.append('\\'); - break; - case '/': - sb.append('/'); - break; - case 'b': - sb.append('\b'); - break; - case 'f': - sb.append('\f'); - break; - case 'n': - sb.append('\n'); - break; - case 'r': - sb.append('\r'); - break; - case 't': - sb.append('\t'); - break; - case 'u': { - // kind of absurdly slow, but screw it for now - char[] a = new char[4]; - for (int i = 0; i < 4; ++i) { - int c = nextCharRaw(); - if (c == -1) - throw problem("End of input but expecting 4 hex digits for \\uXXXX escape"); - a[i] = (char) c; - } - String digits = new String(a); - sbOrig.append(a); - try { - sb.appendCodePoint(Integer.parseInt(digits, 16)); - } catch (NumberFormatException e) { - throw problem(digits, String.format( - "Malformed hex digits after \\u escape in string: '%s'", digits), e); - } - } - break; - default: - throw problem( - asString(escaped), - String.format( - "backslash followed by '%s', this is not a valid escape sequence (quoted strings use JSON escaping, so use double-backslash \\\\ for literal backslash)", - asString(escaped))); - } - } - - private void appendTripleQuotedString(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { - // we are after the opening triple quote and need to consume the - // close triple - int consecutiveQuotes = 0; - for (;;) { - int c = nextCharRaw(); - - if (c == '"') { - consecutiveQuotes += 1; - } else if (consecutiveQuotes >= 3) { - // the last three quotes end the string and the others are - // kept. - sb.setLength(sb.length() - 3); - putBack(c); - break; - } else { - consecutiveQuotes = 0; - if (c == -1) - throw problem("End of input but triple-quoted string was still open"); - else if (c == '\n') { - // keep the line number accurate - lineNumber += 1; - lineOrigin = origin.withLineNumber(lineNumber); - } - } - - sb.appendCodePoint(c); - sbOrig.appendCodePoint(c); - } - } - - private com.gu.typesafe.config.impl.Token pullQuotedString() throws ProblemException { - // the open quote has already been consumed - StringBuilder sb = new StringBuilder(); - - // We need a second string builder to keep track of escape characters. - // We want to return them exactly as they appeared in the original text, - // which means we will need a new StringBuilder to escape escape characters - // so we can also keep the actual value of the string. This is gross. - StringBuilder sbOrig = new StringBuilder(); - sbOrig.appendCodePoint('"'); - - while (true) { - int c = nextCharRaw(); - if (c == -1) - throw problem("End of input but string quote was still open"); - - if (c == '\\') { - pullEscapeSequence(sb, sbOrig); - } else if (c == '"') { - sbOrig.appendCodePoint(c); - break; - } else if (ConfigImplUtil.isC0Control(c)) { - throw problem(asString(c), "JSON does not allow unescaped " + asString(c) - + " in quoted strings, use a backslash escape"); - } else { - sb.appendCodePoint(c); - sbOrig.appendCodePoint(c); - } - } - - // maybe switch to triple-quoted string, sort of hacky... - if (sb.length() == 0) { - int third = nextCharRaw(); - if (third == '"') { - sbOrig.appendCodePoint(third); - appendTripleQuotedString(sb, sbOrig); - } else { - putBack(third); - } - - } - return com.gu.typesafe.config.impl.Tokens.newString(lineOrigin, sb.toString(), sbOrig.toString()); - } - - private com.gu.typesafe.config.impl.Token pullPlusEquals() throws ProblemException { - // the initial '+' has already been consumed - int c = nextCharRaw(); - if (c != '=') { - throw problem(asString(c), "'+' not followed by =, '" + asString(c) - + "' not allowed after '+'", true /* suggestQuotes */); - } - return com.gu.typesafe.config.impl.Tokens.PLUS_EQUALS; - } - - private com.gu.typesafe.config.impl.Token pullSubstitution() throws ProblemException { - // the initial '$' has already been consumed - ConfigOrigin origin = lineOrigin; - int c = nextCharRaw(); - if (c != '{') { - throw problem(asString(c), "'$' not followed by {, '" + asString(c) - + "' not allowed after '$'", true /* suggestQuotes */); - } - - boolean optional = false; - c = nextCharRaw(); - if (c == '?') { - optional = true; - } else { - putBack(c); - } - - WhitespaceSaver saver = new WhitespaceSaver(); - List expression = new ArrayList(); - - com.gu.typesafe.config.impl.Token t; - do { - t = pullNextToken(saver); - - // note that we avoid validating the allowed tokens inside - // the substitution here; we even allow nested substitutions - // in the tokenizer. The parser sorts it out. - if (t == com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY) { - // end the loop, done! - break; - } else if (t == com.gu.typesafe.config.impl.Tokens.END) { - throw problem(origin, - "Substitution ${ was not closed with a }"); - } else { - com.gu.typesafe.config.impl.Token whitespace = saver.check(t, origin, lineNumber); - if (whitespace != null) - expression.add(whitespace); - expression.add(t); - } - } while (true); - - return com.gu.typesafe.config.impl.Tokens.newSubstitution(origin, optional, expression); - } - - private com.gu.typesafe.config.impl.Token pullNextToken(WhitespaceSaver saver) throws ProblemException { - int c = nextCharAfterWhitespace(saver); - if (c == -1) { - return com.gu.typesafe.config.impl.Tokens.END; - } else if (c == '\n') { - // newline tokens have the just-ended line number - com.gu.typesafe.config.impl.Token line = com.gu.typesafe.config.impl.Tokens.newLine(lineOrigin); - lineNumber += 1; - lineOrigin = origin.withLineNumber(lineNumber); - return line; - } else { - com.gu.typesafe.config.impl.Token t; - if (startOfComment(c)) { - t = pullComment(c); - } else { - switch (c) { - case '"': - t = pullQuotedString(); - break; - case '$': - t = pullSubstitution(); - break; - case ':': - t = com.gu.typesafe.config.impl.Tokens.COLON; - break; - case ',': - t = com.gu.typesafe.config.impl.Tokens.COMMA; - break; - case '=': - t = com.gu.typesafe.config.impl.Tokens.EQUALS; - break; - case '{': - t = com.gu.typesafe.config.impl.Tokens.OPEN_CURLY; - break; - case '}': - t = com.gu.typesafe.config.impl.Tokens.CLOSE_CURLY; - break; - case '[': - t = com.gu.typesafe.config.impl.Tokens.OPEN_SQUARE; - break; - case ']': - t = com.gu.typesafe.config.impl.Tokens.CLOSE_SQUARE; - break; - case '+': - t = pullPlusEquals(); - break; - default: - t = null; - break; - } - - if (t == null) { - if (firstNumberChars.indexOf(c) >= 0) { - t = pullNumber(c); - } else if (notInUnquotedText.indexOf(c) >= 0) { - throw problem(asString(c), "Reserved character '" + asString(c) - + "' is not allowed outside quotes", true /* suggestQuotes */); - } else { - putBack(c); - t = pullUnquotedText(); - } - } - } - - if (t == null) - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "bug: failed to generate next token"); - - return t; - } - } - - private static boolean isSimpleValue(com.gu.typesafe.config.impl.Token t) { - if (com.gu.typesafe.config.impl.Tokens.isSubstitution(t) || com.gu.typesafe.config.impl.Tokens.isUnquotedText(t) - || com.gu.typesafe.config.impl.Tokens.isValue(t)) { - return true; - } else { - return false; - } - } - - private void queueNextToken() throws ProblemException { - com.gu.typesafe.config.impl.Token t = pullNextToken(whitespaceSaver); - com.gu.typesafe.config.impl.Token whitespace = whitespaceSaver.check(t, origin, lineNumber); - if (whitespace != null) - tokens.add(whitespace); - - tokens.add(t); - } - - @Override - public boolean hasNext() { - return !tokens.isEmpty(); - } - - @Override - public com.gu.typesafe.config.impl.Token next() { - com.gu.typesafe.config.impl.Token t = tokens.remove(); - if (tokens.isEmpty() && t != com.gu.typesafe.config.impl.Tokens.END) { - try { - queueNextToken(); - } catch (ProblemException e) { - tokens.add(e.problem()); - } - if (tokens.isEmpty()) - throw new ConfigException.BugOrBroken( - "bug: tokens queue should not be empty here"); - } - return t; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "Does not make sense to remove items from token stream"); - } - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Tokens.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Tokens.java deleted file mode 100644 index cf1d311b23f..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Tokens.java +++ /dev/null @@ -1,521 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.List; - -import com.gu.typesafe.config.ConfigException; -import com.gu.typesafe.config.ConfigOrigin; -import com.gu.typesafe.config.ConfigValueType; - -/* FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous. */ -final class Tokens { - static private class Value extends Token { - - final private com.gu.typesafe.config.impl.AbstractConfigValue value; - - Value(com.gu.typesafe.config.impl.AbstractConfigValue value) { - this(value, null); - } - - Value(com.gu.typesafe.config.impl.AbstractConfigValue value, String origText) { - super(com.gu.typesafe.config.impl.TokenType.VALUE, value.origin(), origText); - this.value = value; - } - - com.gu.typesafe.config.impl.AbstractConfigValue value() { - return value; - } - - @Override - public String toString() { - if (value().resolveStatus() == ResolveStatus.RESOLVED) - return "'" + value().unwrapped() + "' (" + value.valueType().name() + ")"; - else - return "'' (" + value.valueType().name() + ")"; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof Value; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && ((Value) other).value.equals(value); - } - - @Override - public int hashCode() { - return 41 * (41 + super.hashCode()) + value.hashCode(); - } - } - - static private class Line extends Token { - Line(com.gu.typesafe.config.ConfigOrigin origin) { - super(com.gu.typesafe.config.impl.TokenType.NEWLINE, origin); - } - - @Override - public String toString() { - return "'\\n'@" + lineNumber(); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof Line; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && ((Line) other).lineNumber() == lineNumber(); - } - - @Override - public int hashCode() { - return 41 * (41 + super.hashCode()) + lineNumber(); - } - - @Override - public String tokenText() { - return "\n"; - } - } - - // This is not a Value, because it requires special processing - static private class UnquotedText extends Token { - final private String value; - - UnquotedText(com.gu.typesafe.config.ConfigOrigin origin, String s) { - super(com.gu.typesafe.config.impl.TokenType.UNQUOTED_TEXT, origin); - this.value = s; - } - - String value() { - return value; - } - - @Override - public String toString() { - return "'" + value + "'"; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof UnquotedText; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) - && ((UnquotedText) other).value.equals(value); - } - - @Override - public int hashCode() { - return 41 * (41 + super.hashCode()) + value.hashCode(); - } - - @Override - public String tokenText() { - return value; - } - } - - static private class IgnoredWhitespace extends Token { - final private String value; - - IgnoredWhitespace(com.gu.typesafe.config.ConfigOrigin origin, String s) { - super(com.gu.typesafe.config.impl.TokenType.IGNORED_WHITESPACE, origin); - this.value = s; - } - - @Override - public String toString() { return "'" + value + "' (WHITESPACE)"; } - - @Override - protected boolean canEqual(Object other) { - return other instanceof IgnoredWhitespace; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) - && ((IgnoredWhitespace) other).value.equals(value); - } - - @Override - public int hashCode() { - return 41 * (41 + super.hashCode()) + value.hashCode(); - } - - @Override - public String tokenText() { - return value; - } - } - - static private class Problem extends Token { - final private String what; - final private String message; - final private boolean suggestQuotes; - final private Throwable cause; - - Problem(com.gu.typesafe.config.ConfigOrigin origin, String what, String message, boolean suggestQuotes, - Throwable cause) { - super(com.gu.typesafe.config.impl.TokenType.PROBLEM, origin); - this.what = what; - this.message = message; - this.suggestQuotes = suggestQuotes; - this.cause = cause; - } - - String what() { - return what; - } - - String message() { - return message; - } - - boolean suggestQuotes() { - return suggestQuotes; - } - - Throwable cause() { - return cause; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('\''); - sb.append(what); - sb.append('\''); - sb.append(" ("); - sb.append(message); - sb.append(")"); - return sb.toString(); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof Problem; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && ((Problem) other).what.equals(what) - && ((Problem) other).message.equals(message) - && ((Problem) other).suggestQuotes == suggestQuotes - && ConfigImplUtil.equalsHandlingNull(((Problem) other).cause, cause); - } - - @Override - public int hashCode() { - int h = 41 * (41 + super.hashCode()); - h = 41 * (h + what.hashCode()); - h = 41 * (h + message.hashCode()); - h = 41 * (h + Boolean.valueOf(suggestQuotes).hashCode()); - if (cause != null) - h = 41 * (h + cause.hashCode()); - return h; - } - } - - static private abstract class Comment extends Token { - final private String text; - - Comment(com.gu.typesafe.config.ConfigOrigin origin, String text) { - super(com.gu.typesafe.config.impl.TokenType.COMMENT, origin); - this.text = text; - } - - final static class DoubleSlashComment extends Comment { - DoubleSlashComment(com.gu.typesafe.config.ConfigOrigin origin, String text) { - super(origin, text); - } - - @Override - public String tokenText() { - return "//" + super.text; - } - } - - final static class HashComment extends Comment { - HashComment(com.gu.typesafe.config.ConfigOrigin origin, String text) { - super(origin, text); - } - - @Override - public String tokenText() { - return "#" + super.text; - } - } - - String text() { - return text; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("'#"); - sb.append(text); - sb.append("' (COMMENT)"); - return sb.toString(); - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof Comment; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && ((Comment) other).text.equals(text); - } - - @Override - public int hashCode() { - int h = 41 * (41 + super.hashCode()); - h = 41 * (h + text.hashCode()); - return h; - } - } - - // This is not a Value, because it requires special processing - static private class Substitution extends Token { - final private boolean optional; - final private List value; - - Substitution(com.gu.typesafe.config.ConfigOrigin origin, boolean optional, List expression) { - super(com.gu.typesafe.config.impl.TokenType.SUBSTITUTION, origin); - this.optional = optional; - this.value = expression; - } - - boolean optional() { - return optional; - } - - List value() { - return value; - } - - @Override - public String tokenText() { - return "${" + (this.optional? "?" : "") + Tokenizer.render(this.value.iterator()) + "}"; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (Token t : value) { - sb.append(t.toString()); - } - return "'${" + sb.toString() + "}'"; - } - - @Override - protected boolean canEqual(Object other) { - return other instanceof Substitution; - } - - @Override - public boolean equals(Object other) { - return super.equals(other) - && ((Substitution) other).value.equals(value); - } - - @Override - public int hashCode() { - return 41 * (41 + super.hashCode()) + value.hashCode(); - } - } - - static boolean isValue(Token token) { - return token instanceof Value; - } - - static com.gu.typesafe.config.impl.AbstractConfigValue getValue(Token token) { - if (token instanceof Value) { - return ((Value) token).value(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "tried to get value of non-value token " + token); - } - } - - static boolean isValueWithType(Token t, ConfigValueType valueType) { - return isValue(t) && getValue(t).valueType() == valueType; - } - - static boolean isNewline(Token token) { - return token instanceof Line; - } - - static boolean isProblem(Token token) { - return token instanceof Problem; - } - - static String getProblemWhat(Token token) { - if (token instanceof Problem) { - return ((Problem) token).what(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to get problem what from " + token); - } - } - - static String getProblemMessage(Token token) { - if (token instanceof Problem) { - return ((Problem) token).message(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to get problem message from " + token); - } - } - - static boolean getProblemSuggestQuotes(Token token) { - if (token instanceof Problem) { - return ((Problem) token).suggestQuotes(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to get problem suggestQuotes from " - + token); - } - } - - static Throwable getProblemCause(Token token) { - if (token instanceof Problem) { - return ((Problem) token).cause(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to get problem cause from " + token); - } - } - - static boolean isComment(Token token) { - return token instanceof Comment; - } - - static String getCommentText(Token token) { - if (token instanceof Comment) { - return ((Comment) token).text(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken("tried to get comment text from " + token); - } - } - - static boolean isUnquotedText(Token token) { - return token instanceof UnquotedText; - } - - static String getUnquotedText(Token token) { - if (token instanceof UnquotedText) { - return ((UnquotedText) token).value(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "tried to get unquoted text from " + token); - } - } - - static boolean isIgnoredWhitespace(Token token) { - return token instanceof IgnoredWhitespace; - } - - static boolean isSubstitution(Token token) { - return token instanceof Substitution; - } - - static List getSubstitutionPathExpression(Token token) { - if (token instanceof Substitution) { - return ((Substitution) token).value(); - } else { - throw new com.gu.typesafe.config.ConfigException.BugOrBroken( - "tried to get substitution from " + token); - } - } - - static boolean getSubstitutionOptional(Token token) { - if (token instanceof Substitution) { - return ((Substitution) token).optional(); - } else { - throw new ConfigException.BugOrBroken("tried to get substitution optionality from " - + token); - } - } - - final static Token START = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.START, "start of file", ""); - final static Token END = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.END, "end of file", ""); - final static Token COMMA = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.COMMA, "','", ","); - final static Token EQUALS = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.EQUALS, "'='", "="); - final static Token COLON = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.COLON, "':'", ":"); - final static Token OPEN_CURLY = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.OPEN_CURLY, "'{'", "{"); - final static Token CLOSE_CURLY = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.CLOSE_CURLY, "'}'", "}"); - final static Token OPEN_SQUARE = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.OPEN_SQUARE, "'['", "["); - final static Token CLOSE_SQUARE = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.CLOSE_SQUARE, "']'", "]"); - final static Token PLUS_EQUALS = Token.newWithoutOrigin(com.gu.typesafe.config.impl.TokenType.PLUS_EQUALS, "'+='", "+="); - - static Token newLine(com.gu.typesafe.config.ConfigOrigin origin) { - return new Line(origin); - } - - static Token newProblem(com.gu.typesafe.config.ConfigOrigin origin, String what, String message, - boolean suggestQuotes, Throwable cause) { - return new Problem(origin, what, message, suggestQuotes, cause); - } - - static Token newCommentDoubleSlash(com.gu.typesafe.config.ConfigOrigin origin, String text) { - return new Comment.DoubleSlashComment(origin, text); - } - - static Token newCommentHash(com.gu.typesafe.config.ConfigOrigin origin, String text) { - return new Comment.HashComment(origin, text); - } - - static Token newUnquotedText(com.gu.typesafe.config.ConfigOrigin origin, String s) { - return new UnquotedText(origin, s); - } - - static Token newIgnoredWhitespace(com.gu.typesafe.config.ConfigOrigin origin, String s) { - return new IgnoredWhitespace(origin, s); - } - - static Token newSubstitution(com.gu.typesafe.config.ConfigOrigin origin, boolean optional, List expression) { - return new Substitution(origin, optional, expression); - } - - static Token newValue(com.gu.typesafe.config.impl.AbstractConfigValue value) { - return new Value(value); - } - static Token newValue(com.gu.typesafe.config.impl.AbstractConfigValue value, String origText) { - return new Value(value, origText); - } - - static Token newString(com.gu.typesafe.config.ConfigOrigin origin, String value, String origText) { - return newValue(new ConfigString.Quoted(origin, value), origText); - } - - static Token newInt(com.gu.typesafe.config.ConfigOrigin origin, int value, String origText) { - return newValue(com.gu.typesafe.config.impl.ConfigNumber.newNumber(origin, value, - origText), origText); - } - - static Token newDouble(com.gu.typesafe.config.ConfigOrigin origin, double value, - String origText) { - return newValue(com.gu.typesafe.config.impl.ConfigNumber.newNumber(origin, value, - origText), origText); - } - - static Token newLong(com.gu.typesafe.config.ConfigOrigin origin, long value, String origText) { - return newValue(com.gu.typesafe.config.impl.ConfigNumber.newNumber(origin, value, - origText), origText); - } - - static Token newNull(com.gu.typesafe.config.ConfigOrigin origin) { - return newValue(new com.gu.typesafe.config.impl.ConfigNull(origin), "null"); - } - - static Token newBoolean(ConfigOrigin origin, boolean value) { - return newValue(new ConfigBoolean(origin, value), "" + value); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/Unmergeable.java b/scripts/src/main/java/com/gu/typesafe/config/impl/Unmergeable.java deleted file mode 100644 index 443cc5d312c..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/Unmergeable.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (C) 2011-2012 Typesafe Inc. - */ -package com.gu.typesafe.config.impl; - -import java.util.Collection; - -/** - * Interface that tags a ConfigValue that is not mergeable until after - * substitutions are resolved. Basically these are special ConfigValue that - * never appear in a resolved tree, like {@link ConfigSubstitution} and - * {@link ConfigDelayedMerge}. - */ -interface Unmergeable { - Collection unmergedValues(); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/impl/package.html b/scripts/src/main/java/com/gu/typesafe/config/impl/package.html deleted file mode 100644 index 52592b0dba1..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/impl/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - -

-Internal implementation details that can change ABI at any time. -

- -

-Please check out the {@link com.typesafe.config.Config public API} instead, unless -you're interested in browsing implementation details. None of the ABI -under impl has any guarantees; it will change whenever someone -feels like changing it. If you feel you need access to something -in impl, please -file a feature request. -

- - - diff --git a/scripts/src/main/java/com/gu/typesafe/config/package.html b/scripts/src/main/java/com/gu/typesafe/config/package.html deleted file mode 100644 index 8f5d9f45474..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/package.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - -

-An API for loading and using configuration files, see the project site -for more information. -

- -

-Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use -it with methods in the {@link com.typesafe.config.Config} interface. Configuration may be in the form of JSON files, -Java properties, or HOCON files; you may also -build your own configuration in code or from your own file formats. -

- -

-An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place -its configuration in "application.conf" on the classpath. -If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()} -there's no need to pass a configuration to your libraries -and frameworks, as long as they all default to this same default, which they should. -
Example application code: Java and Scala. -
Showing a couple of more special-purpose features, a more complex example: Java and Scala. -

- -

-A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a -{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided, -call {@link com.typesafe.config.ConfigFactory#load()} -to get the default one. Typically a library might offer two constructors, one with a Config parameter -and one which uses {@link com.typesafe.config.ConfigFactory#load()}. -
Example library code: Java and Scala. -

- -

-Check out the full examples directory on GitHub. -

- -

-What else to read: -

    -
  • The overview documentation for interface {@link com.typesafe.config.Config}.
  • -
  • The README for the library.
  • -
  • If you want to use .conf files in addition to .json and .properties, - see the README for some short examples - and the full HOCON spec for the long version.
  • -
-

- - - diff --git a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocument.java b/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocument.java deleted file mode 100644 index 6b04f8f7084..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocument.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.gu.typesafe.config.parser; - -import com.gu.typesafe.config.ConfigValue; - -/** - * Represents an individual HOCON or JSON file, preserving all - * formatting and syntax details. This can be used to replace - * individual values and exactly render the original text of the - * input. - * - *

- * Because this object is immutable, it is safe to use from multiple threads and - * there's no need for "defensive copies." - * - *

- * Do not implement interface {@code ConfigDocument}; it should only be - * implemented by the config library. Arbitrary implementations will not work - * because the library internals assume a specific concrete implementation. - * Also, this interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigDocument { - /** - * Returns a new ConfigDocument that is a copy of the current ConfigDocument, - * but with the desired value set at the desired path. If the path exists, it will - * remove all duplicates before the final occurrence of the path, and replace the value - * at the final occurrence of the path. If the path does not exist, it will be added. If - * the document has an array as the root value, an exception will be thrown. - * - * @param path the path at which to set the desired value - * @param newValue the value to set at the desired path, represented as a string. This - * string will be parsed into a ConfigNode using the same options used to - * parse the entire document, and the text will be inserted - * as-is into the document. Leading and trailing comments, whitespace, or - * newlines are not allowed, and if present an exception will be thrown. - * If a concatenation is passed in for newValue but the document was parsed - * with JSON, the first value in the concatenation will be parsed and inserted - * into the ConfigDocument. - * @return a copy of the ConfigDocument with the desired value at the desired path - */ - ConfigDocument withValueText(String path, String newValue); - - /** - * Returns a new ConfigDocument that is a copy of the current - * ConfigDocument, but with the desired value set at the - * desired path. Works like {@link #withValueText(String, String)}, - * but takes a ConfigValue instead of a string. - * - * @param path the path at which to set the desired value - * @param newValue the value to set at the desired path, represented as a ConfigValue. - * The rendered text of the ConfigValue will be inserted into the - * ConfigDocument. - * @return a copy of the ConfigDocument with the desired value at the desired path - */ - ConfigDocument withValue(String path, ConfigValue newValue); - - /** - * Returns a new ConfigDocument that is a copy of the current ConfigDocument, but with - * all values at the desired path removed. If the path does not exist in the document, - * a copy of the current document will be returned. If there is an array at the root, an exception - * will be thrown. - * - * @param path the path to remove from the document - * @return a copy of the ConfigDocument with the desired value removed from the document. - */ - ConfigDocument withoutPath(String path); - - /** - * Returns a boolean indicating whether or not a ConfigDocument has a value at the desired path. - * null counts as a value for purposes of this check. - * @param path the path to check - * @return true if the path exists in the document, otherwise false - */ - boolean hasPath(String path); - - /** - * The original text of the input, modified if necessary with - * any replaced or added values. - * @return the modified original text - */ - String render(); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocumentFactory.java b/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocumentFactory.java deleted file mode 100644 index 88801c5aad9..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigDocumentFactory.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.gu.typesafe.config.parser; - -import com.gu.typesafe.config.ConfigException; - -import com.gu.typesafe.config.ConfigParseOptions; -import com.gu.typesafe.config.impl.Parseable; - -import java.io.File; -import java.io.Reader; - -/** - * Factory for creating {@link - * ConfigDocument} instances. - */ -public final class ConfigDocumentFactory { - - /** - * Parses a Reader into a ConfigDocument instance. - * - * @param reader - * the reader to parse - * @param options - * parse options to control how the reader is interpreted - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static ConfigDocument parseReader(Reader reader, com.gu.typesafe.config.ConfigParseOptions options) { - return Parseable.newReader(reader, options).parseConfigDocument(); - } - - /** - * Parses a reader into a Config instance as with - * {@link #parseReader(Reader, com.gu.typesafe.config.ConfigParseOptions)} but always uses the - * default parse options. - * - * @param reader - * the reader to parse - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static ConfigDocument parseReader(Reader reader) { - return parseReader(reader, com.gu.typesafe.config.ConfigParseOptions.defaults()); - } - - /** - * Parses a file into a ConfigDocument instance. - * - * @param file - * the file to parse - * @param options - * parse options to control how the file is interpreted - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static ConfigDocument parseFile(File file, com.gu.typesafe.config.ConfigParseOptions options) { - return Parseable.newFile(file, options).parseConfigDocument(); - } - - /** - * Parses a file into a ConfigDocument instance as with - * {@link #parseFile(File, com.gu.typesafe.config.ConfigParseOptions)} but always uses the - * default parse options. - * - * @param file - * the file to parse - * @return the parsed configuration - * @throws ConfigException on IO or parse errors - */ - public static ConfigDocument parseFile(File file) { - return parseFile(file, com.gu.typesafe.config.ConfigParseOptions.defaults()); - } - - /** - * Parses a string which should be valid HOCON or JSON. - * - * @param s string to parse - * @param options parse options - * @return the parsed configuration - */ - public static ConfigDocument parseString(String s, com.gu.typesafe.config.ConfigParseOptions options) { - return Parseable.newString(s, options).parseConfigDocument(); - } - - /** - * Parses a string (which should be valid HOCON or JSON). Uses the - * default parse options. - * - * @param s string to parse - * @return the parsed configuration - */ - public static ConfigDocument parseString(String s) { - return parseString(s, ConfigParseOptions.defaults()); - } -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigNode.java b/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigNode.java deleted file mode 100644 index f47187737c8..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/parser/ConfigNode.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (C) 2015 Typesafe Inc. - */ -package com.gu.typesafe.config.parser; - -/** - * A node in the syntax tree for a HOCON or JSON document. - * - *

- * Note: at present there is no way to obtain an instance of this interface, so - * please ignore it. A future release will make syntax tree nodes available in - * the public API. If you are interested in working on it, please see: https://github.com/lightbend/config/issues/300 - * - *

- * Because this object is immutable, it is safe to use from multiple threads and - * there's no need for "defensive copies." - * - *

- * Do not implement interface {@code ConfigNode}; it should only be - * implemented by the config library. Arbitrary implementations will not work - * because the library internals assume a specific concrete implementation. - * Also, this interface is likely to grow new methods over time, so third-party - * implementations will break. - */ -public interface ConfigNode { - /** - * The original text of the input which was used to form this particular - * node. - * - * @return the original text used to form this node as a String - */ - public String render(); -} diff --git a/scripts/src/main/java/com/gu/typesafe/config/parser/package.html b/scripts/src/main/java/com/gu/typesafe/config/parser/package.html deleted file mode 100644 index b0e7b3cea49..00000000000 --- a/scripts/src/main/java/com/gu/typesafe/config/parser/package.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - -

-This package supplies a raw parser and syntax tree for individual HOCON and JSON -files. You do not want this package for everyday config in your app: see -the com.typesafe.config package instead. You would use the raw -parser if you're doing something like reading, modifying, and re-saving a config -file. For info on the main config API this parser is a part of, -see the project site. -

- -

- For working with the raw syntax tree, some important classes are: -

    -
  • {@link com.typesafe.config.parser.ConfigDocument} - a loaded HOCON - or JSON document
  • -
  • {@link com.typesafe.config.parser.ConfigDocumentFactory} - - static methods to instantiate a document
  • -
  • {@link com.typesafe.config.parser.ConfigNode} - syntax node - in a document
  • -
-

- - - diff --git a/scripts/src/main/resources/logback.xml b/scripts/src/main/resources/logback.xml deleted file mode 100644 index 0aeb0a98a6e..00000000000 --- a/scripts/src/main/resources/logback.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %-5relative %-5level %logger{35} - %msg%n - - - - - - - - - diff --git a/scripts/src/main/scala/com/gu/mediaservice/lib/JsonValueCodecJsValue.scala b/scripts/src/main/scala/com/gu/mediaservice/lib/JsonValueCodecJsValue.scala deleted file mode 100644 index 0540dcdc851..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/lib/JsonValueCodecJsValue.scala +++ /dev/null @@ -1,107 +0,0 @@ -package com.gu.mediaservice.lib - -import com.github.plokhotnyuk.jsoniter_scala.core.{JsonReader, JsonValueCodec, JsonWriter} -import _root_.play.api.libs.json.{JsArray, JsBoolean, JsFalse, JsNull, JsNumber, JsObject, JsString, JsTrue, JsValue} - -import scala.collection.IndexedSeq - -object JsonValueCodecJsValue { - - implicit def jsValueCodec: JsonValueCodec[JsValue] = { - new JsonValueCodec[JsValue] { - - /** - * The implementation was borrowed from: https://github.com/plokhotnyuk/jsoniter-scala/blob/e80d51019b39efacff9e695de97dce0c23ae9135/jsoniter-scala-benchmark/src/main/scala/io/circe/CirceJsoniter.scala - * and adapted to meet PlayJson criteria. - */ - def decodeValue(in: JsonReader, default: JsValue): JsValue = { - val b = in.nextToken() - if (b == 'n') in.readNullOrError(default, "expected `null` value") - else if (b == '"') { - in.rollbackToken() - JsString(in.readString(null)) - } else if (b == 'f' || b == 't') { - in.rollbackToken() - if (in.readBoolean()) JsTrue else JsFalse - } else if ((b >= '0' && b <= '9') || b == '-') { - in.rollbackToken() - val bigDecimal = in.readBigDecimal(null) - JsNumber(bigDecimal) - } else if (b == '[') { - val array: IndexedSeq[JsValue] = - if (in.isNextToken(']')) new Array[JsValue](0) - else { - in.rollbackToken() - var i = 0 - var arr = new Array[JsValue](4) - do { - if (i == arr.length) arr = java.util.Arrays.copyOf(arr, i << 1) - arr(i) = decodeValue(in, default) - i += 1 - } while (in.isNextToken(',')) - - if (in.isCurrentToken(']')) - if (i == arr.length) arr else java.util.Arrays.copyOf(arr, i) - else in.arrayEndOrCommaError() - } - JsArray(array) - } else if (b == '{') { - /* - * Because of DoS vulnerability in Scala 2.12 HashMap https://github.com/scala/bug/issues/11203 - * we use a Java LinkedHashMap because it better handles hash code collisions for Comparable keys. - */ - val kvs = - if (in.isNextToken('}')) new java.util.LinkedHashMap[String, JsValue]() - else { - val underlying = new java.util.LinkedHashMap[String, JsValue]() - in.rollbackToken() - do { - underlying.put(in.readKeyAsString(), decodeValue(in, default)) - } while (in.isNextToken(',')) - - if (!in.isCurrentToken('}')) - in.objectEndOrCommaError() - - underlying - } - import scala.jdk.CollectionConverters._ - JsObject(kvs.asScala) - } else { - in.decodeError("expected JSON value") - } - } - - def encodeValue(jsValue: JsValue, out: JsonWriter): Unit = { - jsValue match { - case JsBoolean(b) => - out.writeVal(b) - case JsFalse => - out.writeVal(false) - case JsTrue => - out.writeVal(true) - case JsString(value) => - out.writeVal(value) - case JsNumber(value) => - out.writeVal(value) - case JsArray(items) => - out.writeArrayStart() - items.foreach(encodeValue(_, out)) - out.writeArrayEnd() - case JsObject(kvs) => - out.writeObjectStart() - kvs.foreach { - case (k, v) => - out.writeKey(k) - encodeValue(v, out) - } - out.writeObjectEnd() - case JsNull => - out.writeNull() - } - } - - val nullValue: JsValue = JsNull - } - } - -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/BackfillEditLastModified.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/BackfillEditLastModified.scala deleted file mode 100644 index 881d532857d..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/BackfillEditLastModified.scala +++ /dev/null @@ -1,188 +0,0 @@ -package com.gu.mediaservice.scripts - -import com.sksamuel.elastic4s.ElasticApi.matchQuery -import com.sksamuel.elastic4s.ElasticDsl._ -import org.joda.time.DateTime -import play.api.libs.json.Json -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.dynamodb.model.{AttributeValue, ScanRequest, UpdateItemRequest} -import software.amazon.awssdk.services.dynamodb.{DynamoDbClient, DynamoDbClientBuilder} -import software.amazon.awssdk.services.s3.S3Client - -import java.io.{File, FileOutputStream, PrintWriter} -import scala.jdk.CollectionConverters._ -import scala.io.Source -import scala.util.Try - -object BackfillEditLastModified extends EsScript { - - private val profile = "media-service" - private val region = Region.EU_WEST_1 - private val credentials = DefaultCredentialsProvider.builder.profileName(profile).build - private val dynamo: DynamoDbClient = DynamoDbClient.builder - .region(region) - .credentialsProvider(credentials) - .build - - private var total = 0 - private var skipped = 0 - private var updated = 0 - - override def run(esUrl: String, args: List[String]): Unit = { - def option(options: List[String], argName: String): (Option[String], List[String]) = { - val arg = options.find(_.startsWith(s"--$argName=")) - val value = arg.map(_.stripPrefix(s"--$argName=")) - value -> options.filterNot(arg.contains) - } - - args match { - case dynamoTable :: fileNamePrefix :: remainder => - val (maybeRun, _) = option(remainder, "run") - val dryRun = !maybeRun.contains("true") - val esClient = new EsClient(esUrl) - try { - backfill(esClient, dynamoTable, fileNamePrefix, dryRun) - } finally { - esClient.client.close() - } - case _ => throw new IllegalArgumentException("Usage: BackfillEditLastModified [--run=true]") - } - } - - def backfill(esClient: EsClient, dynamoTable: String, fileName: String, dryRun: Boolean): Unit = { - val lastFile = new File(s"$fileName.last") - val lastEvaluatedKey = if (lastFile.exists()) { - val source = Source.fromFile(lastFile) - val lastId = try { - source.getLines().toList match { - case id :: totalStr :: _ => - total = totalStr.toInt - Some(id) - case _ => None - } - } finally { - source.close() - } - lastId.map { id => - Map("id" -> AttributeValue.builder.s(id).build).asJava - } - } else None - - val logWriter = new PrintWriter(new FileOutputStream(s"$fileName.$total.log")) - def log(msg: String) = logWriter.println(s"[${DateTime.now}/($total/U:$updated/S:$skipped]: $msg") - - log(s"Starting backfill of $dynamoTable from ${esClient.url}; ${if(dryRun){"DRYRUN ONLY"}else{"RUNNING"}}") - log(s"Last evaluated key: $lastEvaluatedKey") - // scan through edits table (this is the shorter list by far) - val scanIterator = dynamo.scanPaginator( - ScanRequest.builder - .tableName(dynamoTable) - .projectionExpression("id") - .limit(500) - .exclusiveStartKey(lastEvaluatedKey.orNull) - .build - ) - scanIterator - .iterator().asScala - .map { r => - - val lastEvaluatedKey = Some(r.lastEvaluatedKey.asScala) - .filter(_ => r.hasLastEvaluatedKey) - .flatMap(_.get("id")) - .map(_.s()) - - - lastEvaluatedKey -> r.items.asScala - } - .map { case (lek, records) => - lek -> records.flatMap(_.asScala.get("id").map(_.s)) - } - .foreach { case (lek, imageIds) => - imageIds.foreach { id => - // for each edit entry: - // do a simple get on the ES cluster - val maybeLastModified = getUserMetadataLastModified(esClient, id) - // update just the last modified value on the edits table - maybeLastModified match { - case Left(reason) => - skipped += 1 - log(s"$id: Unable to get userMetadataLastModified - $reason") - case Right(lastModified) => - updated += 1 - val result = updateEditRecord(dynamoTable, id, lastModified, dryRun) - log(s"$id: $result") - } - total += 1 - Thread.sleep(10) - } - - lek match { - case Some(id) => - log(s"Last evaluated key $id (batch of ${imageIds.size})") - val logWriter = new PrintWriter(new FileOutputStream(lastFile)) - try { - logWriter.println(id) - logWriter.println(total) - } finally { - logWriter.close() - } - case None => // nowt - } - - } - log(s"Finished. Updated: $updated; Skipped: $skipped") - } - - def getUserMetadataLastModified(esClient: EsClient, id: String): Either[String, DateTime] = { - val client = esClient.client - val index = esClient.currentIndex - val queryType = matchQuery("id", id) - val queryResponse = client.execute({ - search(index) - .query(queryType) - .fetchSource(false) - .sourceInclude( - "id", - "userMetadataLastModified" - ) - }).await - - queryResponse.status match { - case 200 => queryResponse.result.hits.hits.flatMap{ hit => - (Json.parse(hit.sourceAsString) \ "userMetadataLastModified").asOpt[String] - } - .headOption - .toRight("Not in ES document") - .flatMap { lm => - Try(new DateTime(lm)).toEither.left.map { t => - t.toString - } - } - case _ => - client.close() - throw new Exception("Failed performing search query") - } - } - - def updateEditRecord(tableName: String, id: String, lastModified: DateTime, dryRun: Boolean): String = { - val request = UpdateItemRequest.builder - .tableName(tableName) - .key(Map("id" -> AttributeValue.builder.s(id).build).asJava) - .updateExpression("SET lastModified = :lastModified") - .expressionAttributeValues(Map(":lastModified" -> AttributeValue.builder.s(lastModified.toString).build).asJava) - .build - val msg = s"Updated with $lastModified ($request)" - if (dryRun) { - s"Would: $msg" - } else { - dynamo.updateItem(request) - msg - } - } - - override def usageError: Nothing = { - System.err.println("Usage: BackfillEditLastModified [--run=true]") - sys.exit(1) - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/BucketMetadata.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/BucketMetadata.scala deleted file mode 100644 index 1949018042b..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/BucketMetadata.scala +++ /dev/null @@ -1,68 +0,0 @@ -package com.gu.mediaservice.scripts - - -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream -import org.joda.time.DateTime -import play.api.libs.json.{Json, OFormat} -import software.amazon.awssdk.auth.credentials.{DefaultCredentialsProvider, ProfileCredentialsProvider} -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.{HeadObjectRequest, ListObjectsV2Request} - -import java.io.{BufferedWriter, File, FileOutputStream, FileWriter, OutputStreamWriter} -import java.time.Instant -import scala.jdk.CollectionConverters._ - -case class ObjectMetadata(key: String, lastModified: Instant, metadata: Map[String, String]) -object ObjectMetadata { - implicit val format: OFormat[ObjectMetadata] = Json.format[ObjectMetadata] -} - -/** - * Dump selected metadata for all objects in a bucket. - * Given a bucket and an output file this will create a JSON line per object containing the key, lastModified time and - * user metadata. - */ -object BucketMetadata { - def apply(args: List[String]): Unit = { - args match { - case bucketName :: fileName :: Nil => bucketMetadata(bucketName, new File(fileName)) - case _ => throw new IllegalArgumentException("Usage: BucketMetadata ") - } - } - - def bucketMetadata(bucketName: String, outputFile: File) = { - val fileOutputStream = new FileOutputStream(outputFile) - val compressOutputStream = new BZip2CompressorOutputStream(fileOutputStream) - val sw = new OutputStreamWriter(compressOutputStream) - System.err.println(s"Output encoding: ${sw.getEncoding}") - val stream = new BufferedWriter(sw) - - try { - val s3: S3Client = S3Client.builder - .region(Region.EU_WEST_1) - .credentialsProvider(DefaultCredentialsProvider.builder.profileName("media-service").build) - .build - - val results = s3.listObjectsV2Paginator(ListObjectsV2Request.builder.bucket(bucketName).build) - results.iterator().asScala.flatMap { results => - results.contents().asScala - }.map { s3Object => - val headObjectResponse = s3.headObject(HeadObjectRequest.builder.bucket(bucketName).key(s3Object.key).build) - s3Object -> headObjectResponse - }.map { case (s3Object, metadata) => - ObjectMetadata(s3Object.key, metadata.lastModified, metadata.metadata.asScala.toMap) - }.map { md => - Json.stringify(Json.toJson(md)) - }.zipWithIndex - .foreach { case (json, idx) => - if (idx % 1000 == 0) System.err.println(s"${DateTime.now.toString}: ${idx}") - stream.write(json) - stream.newLine() - stream.flush() - } - } finally { - stream.close() - } - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/BulkDeleteS3Files.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/BulkDeleteS3Files.scala deleted file mode 100644 index 3dac2e67826..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/BulkDeleteS3Files.scala +++ /dev/null @@ -1,68 +0,0 @@ -package com.gu.mediaservice.scripts - -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.{Delete, DeleteObjectsRequest, ObjectIdentifier} - -import java.io.{File, PrintWriter} -import scala.io.Source -import scala.jdk.CollectionConverters._ - -object BulkDeleteS3Files { - - private val profile = "media-service" - private val region = Region.EU_WEST_1 - private val credentials = DefaultCredentialsProvider.builder.profileName(profile).build - private val s3: S3Client = S3Client.builder - .region(region) - .credentialsProvider(credentials) - .build - def apply(args: List[String]): Unit = { - args match { - case bucketName :: inputFileName :: auditFileName :: _ => - apply(bucketName, inputFileName, auditFileName) - case _ => throw new IllegalArgumentException("Usage: BulkDeleteS3Files ") - } - } - - def apply(bucketName: String, inputFileName: String, auditFileName: String) = { - - val inputFileSource = Source.fromFile(inputFileName) - val auditFileWriter = new PrintWriter(new File(auditFileName)) - auditFileWriter.println("path, result") - try { - for { - bucketKeys <- inputFileSource.getLines().drop(1 /* column heading */).grouped(1000).toList - objectIdentifiers = bucketKeys.map(key => ObjectIdentifier.builder().key(key.trim().drop(1).dropRight(1)).build()) - batchResult = s3.deleteObjects( - DeleteObjectsRequest.builder() - .bucket(bucketName) - .delete( - Delete.builder() - .objects(objectIdentifiers:_*) - .quiet(false) // so we get success back too - .build() - ) - .build() - ) - successfulDeletesLogLines = batchResult.deleted().asScala.map{ deletedObject => - s"${deletedObject.key()}, DELETED" - } - failedDeletesLogLines = batchResult.errors().asScala.map{ error => - s"${error.key()}, FAILED, ${error.code()}, ${error.message()}" - } - logLine <- successfulDeletesLogLines ++ failedDeletesLogLines - } { - println(logLine) - auditFileWriter.println(logLine) - } - } - finally { - inputFileSource.close() - auditFileWriter.close() - } - - } - -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/ConvertConfig.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/ConvertConfig.scala deleted file mode 100644 index 231595e5313..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/ConvertConfig.scala +++ /dev/null @@ -1,65 +0,0 @@ -package com.gu.mediaservice.scripts - -import java.io.File -import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Path, Paths} - -import com.gu.typesafe.config.{ConfigFactory, ConfigRenderOptions} - -import scala.util.Try - -import scala.jdk.CollectionConverters._ - -object ConvertConfig { - case class Conversion(input: File, output: File) - - def apply(args: List[String]): Unit = { - args match { - case "-f" :: as => convertConfigs(as, overwrite = true) - case as => convertConfigs(as, overwrite = false) - } - } - - private def convertConfigs(args: List[String], overwrite: Boolean): Unit = { - val conversions = args.flatMap { arg => - val input = new File(arg) - assert(input.exists, s"File provided for conversion doesn't exist: $input") - val files: List[File] = if (input.isDirectory) { - val paths: List[Path] = Files.walk(input.toPath).iterator.asScala.toList - val regularFiles = paths.filter(file => Files.isRegularFile(file)) - val propertiesFiles = regularFiles.filter(_.toString.endsWith(".properties")) - val files = propertiesFiles.map(_.toFile) - assert(files.nonEmpty, s"No properties files found to convert in $input") - files - } else { - assert(input.toString.endsWith(".properties"), s"File provided for conversion isn't a java properties input: $input") - List(input) - } - files.map { f => - val output = new File(s"${f.toString.stripSuffix(".properties")}.conf") - if (!overwrite) { - assert(!output.exists, s"Output file for $input already exists: $output") - } - Conversion(f, output) - } - } - - conversions.foreach(conversion => convert(conversion)) - } - - private def convert(conversion: Conversion): Either[Throwable, Unit] = { - for { - config <- Try(ConfigFactory.parseFile(conversion.input)).toEither - options = ConfigRenderOptions.defaults().setOriginComments(false).setJson(false).setCompactKeys(true) - hoconStr = config.root().render(options) - _ <- writeToFile(conversion.output, hoconStr) - } yield () - } - - private def writeToFile(file: File, str: String): Either[Throwable, Path] = { - Try{ - System.err.println(s"Writing config to $file") - Files.write(file.toPath, str.getBytes(StandardCharsets.UTF_8)) - }.toEither - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/DecodeComparator.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/DecodeComparator.scala deleted file mode 100644 index 6d2113dcb19..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/DecodeComparator.scala +++ /dev/null @@ -1,39 +0,0 @@ -package com.gu.mediaservice.scripts - -import com.gu.mediaservice.lib.net.URI -import play.api.libs.json.{JsError, JsSuccess, Json} - -import java.io.File -import scala.io.Source - -/** - * Given a JSON lines file output from BucketMetadata this will verify that metadata is not changed when being - * passed through a URI decode function. This was to check that it is safe to deploy - * https://github.com/guardian/grid/pull/3165 - */ -object DecodeComparator { - def apply(args: List[String]): Unit = { - args match { - case fileName :: Nil => compare(new File(fileName)) - case as => throw new IllegalArgumentException("Usage: DecodeComparator ") - } - } - - def compare(file: File): Unit = { - val source = Source.fromFile(file) - try { - source.getLines().foreach { line => - Json.fromJson[ObjectMetadata](Json.parse(line)) match { - case JsError(errors) => System.err.println(s"Couldn't parse JSON $line") - case JsSuccess(metadata, _) => - metadata.metadata.toList.foreach{ case (key, value) => - val decodedValue = URI.decode(value) - if (value != decodedValue) System.out.println(s"Difference between $key '$value' and '$decodedValue'") - } - } - } - } finally { - source.close() - } - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/EnactS3Changes.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/EnactS3Changes.scala index 26f5490e14f..e69de29bb2d 100644 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/EnactS3Changes.scala +++ b/scripts/src/main/scala/com/gu/mediaservice/scripts/EnactS3Changes.scala @@ -1,196 +0,0 @@ -package com.gu.mediaservice.scripts - -import java.io._ -import org.apache.commons.compress.compressors.bzip2.{BZip2CompressorInputStream, BZip2CompressorOutputStream} -import play.api.libs.json.{JsObject, JsValue, Json, OWrites} - -import scala.io.Source -import org.joda.time.{DateTime, Duration} -import com.gu.mediaservice.lib.JsonValueCodecJsValue.jsValueCodec -import com.github.plokhotnyuk.jsoniter_scala.core._ -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.{CopyObjectRequest, HeadObjectRequest, ListObjectsRequest, ListObjectsV2Request, MetadataDirective, NoSuchKeyException} - -import scala.jdk.CollectionConverters._ -import scala.util.{Failure, Success, Try} - -object EnactS3Changes { - - private val profile = "media-service" - private val region = Region.EU_WEST_1 - private val credentials = DefaultCredentialsProvider.builder.profileName(profile).build - private val s3: S3Client = S3Client.builder - .region(region) - .credentialsProvider(credentials) - .build - - def option(options: List[String], argName: String): (Option[String], List[String]) = { - val arg = options.find(_.startsWith(s"$argName=")) - val value = arg.map(_.stripPrefix(s"$argName=")) - value -> options.filterNot(arg.contains) - } - - def apply(args: List[String]): Unit = { - args match { - case bucketName :: inputFileName :: auditFileName :: options => - val (maybeDrop, optionsAfterDrop) = option(options, "drop") - val (maybeTake, optionsAfterTake) = option(optionsAfterDrop, "take") - val (maybePrefixFilter, unknownOptions) = option(optionsAfterTake, "prefixFilter") - if (unknownOptions.nonEmpty) throw new IllegalArgumentException(s"Didn't recognise options $unknownOptions") - enactS3Changes( - bucketName, - new File(inputFileName), - new File(auditFileName), - maybeDrop.map(_.toInt), - maybeTake.map(_.toInt), - maybePrefixFilter - ) - case _ => throw new IllegalArgumentException("Usage: EnactS3Changes [drop=] [take=] [prefixFilter=]") - } - } - - def getBzipWriter(outputFile: File, append: Boolean = false) = { - val fileOutputStream = new FileOutputStream(outputFile, append) - val compressOutputStream = new BZip2CompressorOutputStream(fileOutputStream) - val sw = new OutputStreamWriter(compressOutputStream) - new BufferedWriter(sw) - } - - case class AuditEntry(status: String, key: String, message: Option[String] = None, details: Option[JsObject] = None) - - object AuditEntry { - def apply(status: String, key: String, message: String): AuditEntry = AuditEntry(status, key, Some(message)) - def apply(status: String, key: String, message: String, details: JsObject): AuditEntry = AuditEntry(status, key, Some(message), Some(details)) - - implicit val writes: OWrites[AuditEntry] = Json.writes[AuditEntry] - def audit(writer: BufferedWriter, entry: AuditEntry): Unit = { - val value = Json.toJsObject(entry) - val json = writeToString[JsValue](value) - writer.append(s"$json\n") - } - val OK = "OK" - val SKIPPED = "SKIPPED" - val ERROR = "ERROR" - } - - def getAuditFileName(auditFile: File, id: Int = 0): File = { - val file = new File(s"${auditFile.getPath}${if (id > 0) s".$id" else ""}") - if (file.exists()) getAuditFileName(auditFile, id+1) else file - } - - def enactS3Changes( - bucketName: String, - inputFile: File, - auditFile: File, - maybeDrop: Option[Int], - maybeTake: Option[Int], - prefixFilter: Option[String]) = { - import AuditEntry._ - - // do this to ensure creds work before starting - s3.listObjectsV2(ListObjectsV2Request.builder.bucket(bucketName).build) - - // reporting - val batchSize = 1000 - val startTime = DateTime.now - var startBatchTime = DateTime.now - var total = 0L - - // auditing - val auditWriter = getBzipWriter(getAuditFileName(auditFile)) - auditWriter.append(s"Enacting changes in $bucketName from $inputFile (dropping: $maybeDrop, taking: $maybeTake, prefixFilter: $prefixFilter)\n") - System.err.println(s"Enacting changes in $bucketName from $inputFile (dropping: $maybeDrop, taking: $maybeTake, prefixFilter: $prefixFilter)") - try { - withSourceFromBzipFile(inputFile) {source => - val lines = source - .getLines() - .map(line => { - val maybeJsValue = Try(readFromString[JsValue](line)).toOption - ( - maybeJsValue.map(_ \ "proposed").flatMap(_.asOpt[ObjectMetadata]), - maybeJsValue.map(_ \ "original").flatMap(_.asOpt[ObjectMetadata]) - ) - }) - val filteredLines = prefixFilter - .map(pf => lines.filter{ - case (Some(proposed), _) => proposed.key.startsWith(pf) - case _ => false - }) - .getOrElse(lines) - val droppedLines = maybeDrop.map(filteredLines.drop).getOrElse(filteredLines) - val linesToProcess = maybeTake.map(droppedLines.take).getOrElse(droppedLines) - - val auditEntries: Iterator[AuditEntry] = linesToProcess - .map { - case (Some(proposed), Some(original)) => - val key = proposed.key - Try {Some(s3.headObject(HeadObjectRequest.builder.bucket(bucketName).key(key).build))}.recover{ - case _:NoSuchKeyException => None - } match { - case Success(Some(headObjectResponse)) => - val check = headObjectResponse.metadata.asScala - if (check!=original.metadata) { - if (check==proposed.metadata) { - AuditEntry(SKIPPED, key, s"Already updated") - } else { - AuditEntry(ERROR, key, s"Metadata doesn't match that expected", Json.obj( - "actual" -> check, - "expected" -> original.metadata - )) - } - } else { - val request = CopyObjectRequest.builder - .sourceBucket(bucketName).sourceKey(key) - .destinationBucket(bucketName).destinationKey(key) - .metadata(proposed.metadata.asJava) - .metadataDirective(MetadataDirective.REPLACE) - .build - Try {s3.copyObject(request)} match { - case Success(_) => AuditEntry(OK, key) - case Failure(e) => AuditEntry(ERROR, key, s"Error whilst copying object ($e)") - } - } - case Success(None) => - AuditEntry(SKIPPED, key, "Object no longer exists") - case Failure(e) => - AuditEntry(ERROR, key, s"Error whilst getting object metadata ($e)") - } - case other => AuditEntry(ERROR, "n/a", s"Unable to parse record", Json.obj("record" -> other.toString)) - } - auditEntries - .map{ entry => - audit(auditWriter, entry) - entry - } - .grouped(batchSize) - .foreach{ auditEntryBatch => - val auditEntries = auditEntryBatch.toList - total = total + auditEntries.size - val now = DateTime.now - val elapsed = new Duration(startTime, now) - val elapsedBatch = new Duration(startBatchTime, now).getMillis - System.err.println(s"${DateTime.now.toString} Processed $total lines") - System.err.println(s"Batch: Processed ${auditEntries.size} in ${elapsedBatch}ms (mean: ${elapsedBatch/auditEntries.size}ms per line)") - System.err.println(s" OK: ${auditEntries.count(_.status==OK)} SKIPPED: ${auditEntries.count(_.status==SKIPPED)} ERROR: ${auditEntries.count(_.status==ERROR)}") - System.err.println(s"Total: Processed $total in ${elapsed.getStandardSeconds}s (mean: ${elapsed.getMillis/total}ms per line)") - startBatchTime = now - } - } - } finally { - auditWriter.close() - } - } - - private def withSourceFromBzipFile[T](file: File)(f: Source => T) = { - val fileInputStream = new FileInputStream(file) - val compressInputStream = new BZip2CompressorInputStream(fileInputStream) - val source = Source.fromInputStream(compressInputStream) - try { - f(source) - } finally { - source.close() - } - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/EsImageMetadata.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/EsImageMetadata.scala deleted file mode 100644 index 76dff10c58b..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/EsImageMetadata.scala +++ /dev/null @@ -1,165 +0,0 @@ -package com.gu.mediaservice.scripts - -import com.sksamuel.elastic4s.ElasticClient -import com.sksamuel.elastic4s.ElasticDsl._ -import com.sksamuel.elastic4s.requests.searches.{SearchHit, SearchResponse} -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream -import play.api.libs.json._ - -import java.io.{BufferedWriter, File, FileOutputStream, OutputStreamWriter} -import java.util.concurrent.TimeUnit -import scala.annotation.tailrec -import scala.concurrent.duration.{Duration, FiniteDuration} -import scala.concurrent.{Await, Future} - -object EsImageMetadata extends EsScript { - override def run(esUrl: String, args: List[String]): Unit = { - val outputFile: File = args match { - case fileName :: Nil => - val file = new File(fileName) - //if (file.exists()) throw new IllegalStateException("This would overwrite a file so bailing now in case that would be bad") - file - case _ => throw new IllegalArgumentException("Usage: BucketMetadata ") - } - - object Client extends EsClient(esUrl) { - } - - val scrollTime = new FiniteDuration(5, TimeUnit.MINUTES) - val scrollSize = 500 - val currentIndex = Client.currentIndex - - System.err.println(s"Using index $currentIndex") - - val fileOutputStream = new FileOutputStream(outputFile) - val compressOutputStream = new BZip2CompressorOutputStream(fileOutputStream) - val sw = new OutputStreamWriter(compressOutputStream) - System.err.println(s"Output encoding: ${sw.getEncoding}") - val writer = new BufferedWriter(sw) - - try { - val initialResults = initialQuery(Client.client, currentIndex, scrollTime, scrollSize) - Await.ready(recurse(initialResults, writer, 0L, 0L), Duration.Inf) - } finally { - Client.client.close() - writer.close() - } - - @tailrec - def recurse(lastResponse: SearchResponse, outputWriter: BufferedWriter, done: Long, warnings: Long): Future[Unit] = { - val newDone = done + scrollSize - val hits = lastResponse.hits.hits - if(hits.nonEmpty) { - val newWarnings = writeMetadata(outputWriter, hits) + warnings - System.err.println(scrollPercentage(lastResponse, newDone, newWarnings)) - val scrollResponse = performScroll(Client.client, lastResponse.scrollId.get, scrollTime) - Thread.sleep(500) - //if (done < 100000) - recurse(scrollResponse, outputWriter, newDone, newWarnings) - //else Future.successful(()) - } else { - System.err.println("No more results found") - Future.successful(()) - } - } - - } - - override def usageError: Nothing = { - System.err.println("Usage: UpdateSettings ") - sys.exit(1) - } - - def scrollPercentage(scroll: SearchResponse, done: Long, warnings: Long): String = { - val total = scroll.hits.total.value - val percentage = (Math.min(done,total).toFloat / total) * 100 - s"Extracted ${Math.min(done,total)} of $total ($percentage%) with $warnings warnings" - } - - def initialQuery(client: ElasticClient, index: String, scrollTime: FiniteDuration, scrollSize: Int) : SearchResponse = { - val queryType = matchAllQuery() - //val queryType = matchQuery("id", "cbaa4f052b1d4ae996e63ee1d07ada0092b4b581") - - val queryResponse = client.execute({ - search(index) - .scroll(scrollTime) - .size(scrollSize) - .query(queryType) - .fetchSource(false) - .sourceInclude( - //.docValues( - "id", - "uploadedBy", - "uploadTime", - "uploadInfo.filename", - "identifiers.*", - "lastModified" - ) - }).await - - queryResponse.status match { - case 200 => queryResponse.result - case _ => - client.close() - throw new Exception("Failed performing search query") - } - } - - def performScroll(client: ElasticClient, scrollId: String, scrollTime: FiniteDuration): SearchResponse = { - val scrollResponse = client.execute({ - searchScroll(scrollId) - .keepAlive(scrollTime) - }).await - - scrollResponse.status match { - case 200 => scrollResponse.result - case _ => - client.close() - throw new Exception("Failed performing bulk index") - } - } - - def writeMetadata(writer: BufferedWriter, hits: Array[SearchHit]): Long = { - def jsObjectToStringMap(obj: JsObject): Map[String, String] = { - obj.fields.collect { - case (key, JsString(value)) => key -> value - }.toMap - } - - val jsonLines = hits.flatMap { hit => - val source = Json.parse(hit.sourceAsString) - (source \ "id").asOpt[String] map { id => - val doc = EsDocumentWithMetadata( - id = id, - lastModified = (source \ "lastModified").asOpt[String], - uploadedBy = (source \ "uploadedBy").asOpt[String], - uploadTime = (source \ "uploadTime").asOpt[String], - fileName = (source \ "uploadInfo" \ "filename").asOpt[String], - identifiers = (source \ "identifiers").asOpt[JsObject].map(jsObjectToStringMap).getOrElse(Map.empty) - ) - Json.stringify(Json.toJson(doc)) - } - } - - jsonLines.foreach{ json => - writer.write(json) - writer.newLine() - } - writer.flush() - - hits.length - jsonLines.length - } -} - -case class EsDocumentWithMetadata( - id: String, - lastModified: Option[String], - uploadedBy: Option[String], - uploadTime: Option[String], - fileName: Option[String], - identifiers: scala.collection.immutable.Map[String, String] = Map.empty - ) - -object EsDocumentWithMetadata { - implicit val formats: Format[EsDocumentWithMetadata] = Json.using[Json.WithDefaultValues].format[EsDocumentWithMetadata] -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/EsScript.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/EsScript.scala deleted file mode 100644 index 26f7d54da1a..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/EsScript.scala +++ /dev/null @@ -1,456 +0,0 @@ -package com.gu.mediaservice.scripts - -import com.gu.mediaservice.lib.elasticsearch.{ElasticSearchClient, Mappings} -import com.sksamuel.elastic4s.ElasticDsl._ -import com.sksamuel.elastic4s.handlers.index.GetIndexResponse -import com.sksamuel.elastic4s.requests.bulk.BulkResponse -import com.sksamuel.elastic4s.requests.indexes.IndexRequest -import com.sksamuel.elastic4s.requests.searches.{SearchHit, SearchResponse} -import com.sksamuel.elastic4s.{Indexes, Response} -import org.joda.time.DateTime -import play.api.libs.json.Json - -import java.io.{File, PrintWriter} -import java.util.concurrent.TimeUnit -import scala.annotation.tailrec -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration, SECONDS} -import scala.concurrent.{Await, Future} - - -object Reindex extends EsScript { - - def run(esUrl: String, args: List[String]) = { - - object IndexClient extends EsClient(esUrl) { - val srcIndexVersionCheck = """images_(\d+)""".r - val srcIndexVersion = this.currentIndex match { - case srcIndexVersionCheck(version) => version.toInt - case _ => 1 - } - val nextIndex = s"${imagesIndexPrefix}_${srcIndexVersion+1}" - } - - def raiseError(msg: String) = { - System.err.println(s"Reindex error on: $esUrl : $msg ") - System.err.println("Exiting...") - IndexClient.client.close() - System.exit(1) - } - - def validateCurrentState(esClient: ElasticSearchClient, from: Option[DateTime]) = { - if(from.exists(_.isAfter(DateTime.now()))) - raiseError("DateTime parameter 'from' must be earlier than the current time" ) - } - - def getArg(argKey: String): Option[String] = { - args.find(_ contains s"${argKey}=") - .map(_ replaceFirst(s"${argKey}=", "")) - } - - val scrollTime = new FiniteDuration(5, TimeUnit.MINUTES) - val scrollSize = 500 - val currentIndex = IndexClient.currentIndex - val newIndex = getArg("NEW_INDEX") match { - case Some(arg) => arg - case None => IndexClient.nextIndex - } - val from = getArg("FROM_TIME") match { - case Some(arg) => Some(DateTime.parse(arg)) - case None => None - } - validateCurrentState(IndexClient, from) - Await.result(reindex(from, IndexClient), Duration.Inf) - println(s"Pointing $esImagesReadAlias to new index: $newIndex") - IndexClient.changeAliasTo(newIndex, currentIndex, esImagesReadAlias) - println(s"Finished reindexing from $currentIndex to $newIndex") - IndexClient.client.close() - - def reindex(from: Option[DateTime], esClient: ElasticSearchClient) : Future[SearchResponse] = { - - def _scroll(scroll: SearchResponse, done: Long = 0): Future[SearchResponse] = { - val client = esClient.client - val currentBatch = done + scrollSize - System.out.println(scrollPercentage(scroll, currentBatch, done)) - - def bulkFromHits(hits: Array[SearchHit]): BulkResponse = { - val bulkRequests: Array[IndexRequest] = hits.map { hit => - indexInto(newIndex) - .withId(hit.id) - .source(hit.sourceAsString) - } - - val bulkResponse = IndexClient.client.execute({ - bulk(bulkRequests) - }).await - - bulkResponse.status match { - case 200 => bulkResponse.result - case _ => { - IndexClient.client.close() - throw new Exception("Failed performing bulk index") - } - } - } - - def scrollPercentage(scroll: SearchResponse, currentBatch: Long, done: Long): String = { - val total = scroll.hits.total.value - // roughly accurate as we're using done, which is relative to scrollSize, rather than the actual number of docs in the new index - val percentage = (Math.min(done,total).toFloat / total) * 100 - s"Reindexing ${Math.min(currentBatch,total)} of $total ($percentage%)" - } - - def performScroll(scrollId: String, scrollTime: FiniteDuration): SearchResponse = { - val scrollResponse = IndexClient.client.execute({ - searchScroll(scrollId) - .keepAlive(scrollTime) - }).await - - scrollResponse.status match { - case 200 => scrollResponse.result - case _ => { - IndexClient.client.close() - throw new Exception("Failed performing bulk index") - } - } - } - - def analyseBulkResponse(bulkResponse: BulkResponse) = { - val successes = bulkResponse.items.filter(item => item.status == 201).map(item => item.id) - val failures = bulkResponse.items.filterNot(item => item.status == 201).map(item => item.id) - println(s"...added ${successes.length}/${bulkResponse.items.length} items in ${bulkResponse.took} ms (${failures.length} failures)") - if (failures.nonEmpty) println(s"......failure IDs: $failures") - } - - val hits = scroll.hits.hits - if(hits.nonEmpty) { - val bulkResponse = bulkFromHits(hits) - analyseBulkResponse(bulkResponse) - val scrollResponse = performScroll(scroll.scrollId.get, scrollTime) - _scroll(scrollResponse, currentBatch) - } else { - println("No more results found") - Future.successful[SearchResponse](scroll) - } - } - - def query(from: Option[DateTime]) : SearchResponse = { - val queryType = from.map(time => - rangeQuery("lastModified").gte(from.get.getMillis).lte(DateTime.now.getMillis) - ).getOrElse( - matchAllQuery() - ) - - val queryResponse = IndexClient.client.execute({ - search(currentIndex) -// .types(Mappings.dummyType) - .scroll(scrollTime) - .size(scrollSize) - .query(queryType) - }).await - - queryResponse.status match { - case 200 => queryResponse.result - case _ => { - IndexClient.client.close() - throw new Exception("Failed performing search query") - } - } - } - - // if no 'from' time parameter is passed, create a new index - if(from.isEmpty) { - IndexClient.createImageIndex(newIndex) - } else { - println(s"Reindexing documents modified since: ${from.toString}") - } - - val startTime = DateTime.now() - println(s"Reindex started at: $startTime") - println(s"Reindexing from: ${IndexClient.currentIndex} to: $newIndex") - val scrollResponse = query(from) - _scroll(scrollResponse) flatMap { case (response: SearchResponse) => - println(s"Pointing ${IndexClient.imagesCurrentAlias} to new index: $newIndex") - IndexClient.changeAliasTo(newIndex, currentIndex) - - val changedDocuments: Long = query(Option(startTime)).hits.total.value - println(s"$changedDocuments changed documents since start") - - if(changedDocuments > 0) { - println(s"Reindexing changes since start time: $startTime") - val recurseResponse = reindex(Option(startTime), esClient) - recurseResponse - } else { - Future.successful(response) - } - } - } - } - - def usageError: Nothing = { - System.err.println("Usage: Reindex error [NEW_INDEX=, FROM_TIME=]") - sys.exit(1) - } -} - -// TODO: add the ability to update a section of the mapping -object UpdateMapping extends EsScript { - - def run(esUrl: String, extraArgs: List[String]): Unit = { - val includeDenseVectors = !extraArgs.contains("--no-dense-vectors") - val remainingArgs = extraArgs.filterNot(_ == "--no-dense-vectors") - - println(s"includeDenseVectorMappings: $includeDenseVectors") - - object MappingsClient extends EsClient(esUrl, includeDenseVectors) { - def updateMappings(specifiedIndex: Option[String]): Unit = { - val index = specifiedIndex.getOrElse(imagesCurrentAlias) - println(s"Updating mapping on index: $index") - - val result = client.execute { - putMapping(Indexes(index)) as { - Mappings.imageMapping(includeDenseVectors).properties - } - }.await - - result.status match { - case 200 => println(Json.prettyPrint(Json.parse(result.body.get))) - case _ => println(s"Failed updating mapping: ${result.error}") - } - - client.close - } - - } - - MappingsClient.updateMappings(remainingArgs.headOption) - } - - def usageError: Nothing = { - System.err.println("Usage: UpdateMapping [] [--no-dense-vectors]") - sys.exit(1) - } -} - -object GetMapping extends EsScript { - - def run(esUrl: String, extraArgs: List[String]): Unit = { - - object MappingsClient extends EsClient(esUrl) { - def getMappings(specifiedIndex: Option[String]): Unit = { - val index = specifiedIndex.getOrElse(imagesCurrentAlias) - println(s"Getting mapping on index: $index") - - val result = Await.result(client.execute( - getMapping(index) - ), Duration(30, SECONDS)) - - result.status match { - case 200 => println(Json.prettyPrint(Json.parse(result.body.get))) - case _ => println(s"Failed getting mapping: ${result.error}") - } - - - client.close() - } - } - - MappingsClient.getMappings(extraArgs.headOption) - } - - def usageError: Nothing = { - System.err.println("Usage: GetMapping ") - sys.exit(1) - } -} - -object UpdateSettings extends EsScript { - - def run(esUrl: String, extraArgs: List[String]): Unit = { - - object SettingsClient extends EsClient(esUrl) { - if (!url.contains("localhost")) { - System.err.println(s"You can only run UpdateSettings on localhost, not '$esUrl'") - System.exit(1) - } - - def updateIdxSettings(specifiedIndex: Option[String]): Unit = { - - val index = specifiedIndex.getOrElse(imagesCurrentAlias) - println(s"Getting mapping on index: $index") - - val settingsToAdd: Map[String, String] = Map("max_result_window" -> "25000") - - val resultFut = for { - _ <- client.execute(closeIndex(index)) - result <- { - client.execute( - updateSettings(index) - .add(settingsToAdd) - ) - } - _ <- client.execute(openIndex(index)) - } yield result - - val result = Await.result(resultFut, Duration(30, SECONDS)) - - result.status match { - case 200 => println(Json.prettyPrint(Json.parse(result.body.get))) - case _ => println(s"Failed updating index settings: ${result.error}") - } - - client.close - } - } - - SettingsClient.updateIdxSettings(extraArgs.headOption) - } - - def usageError: Nothing = { - System.err.println("Usage: UpdateSettings ") - sys.exit(1) - } -} - -object GetSettings extends EsScript { - - def run(esUrl: String, extraArgs: List[String]): Unit = { - - object SettingsClient extends EsClient(esUrl) { - def getIdxSettings(specifiedIndex: Option[String]): Unit = { - val index = specifiedIndex.getOrElse(imagesCurrentAlias) - println(s"Getting settings on index: $index") - - val result = Await.result(client.execute( - getSettings(index) - ), Duration(30, SECONDS)) - - result.status match { - case 200 => println(Json.prettyPrint(Json.parse(result.body.get))) - case _ => println(s"Failed getting settings: ${result.error}") - } - - - client.close() - } - } - - SettingsClient.getIdxSettings(extraArgs.headOption) - } - - def usageError: Nothing = { - System.err.println("Usage: GetMapping ") - sys.exit(1) - } -} -object DownloadAllEsIds extends EsScript { - - def run(esUrl: String, extraArgs: List[String]): Unit = { - - println(s"Getting all ids from $esUrl") - - object Client extends EsClient(esUrl) { - - def writeAllEsIdsToFile(filename: String) = { - - val file = new File(filename) - - val writer = new PrintWriter(file) - - @tailrec - def processResponseAndStartNext(prevResponse: Response[SearchResponse]): Unit = { - if(prevResponse.status != 200) { - writer.close() - client.close() - throw new Exception("Failed performing search query") - } - - println(s"Adding ${prevResponse.result.hits.hits.length} IDs to ${file.getAbsolutePath}") - - prevResponse.result.hits.hits.map(_.id).foreach(writer.println) - - // can't be map/foreach if we want tail recursion (for efficiency) - prevResponse.result.scrollId match { - case None => () // done - case Some(scrollId) => - processResponseAndStartNext( - client.execute( - searchScroll(scrollId) - .keepAlive(10.seconds) - ).await - ) - } - } - - processResponseAndStartNext( - client.execute( - search(currentIndex) - .size(1000) - .storedFields("_id") // ensures we only return the id field - .scroll(1.minute) - ).await - ) - - writer.close() - } - - } - - Client.writeAllEsIdsToFile(extraArgs.headOption.getOrElse(throw new Exception("No filename specified"))) - - } - - - def usageError: Nothing = { - System.err.println("Usage: GetAllEsIds ") - sys.exit(1) - } -} - -abstract class EsScript { - // FIXME: Get from config (no can do as Config is coupled to Play) - final val esImagesAlias = "Images_Current" - final val esImagesReadAlias = "Images_Current" - final val esShards = 5 - final val esReplicas = 0 - - def log(msg: String) = System.out.println(s"[${getClass.getName}]: $msg") - - def apply(args: List[String]): Unit = { - // FIXME: Use Stage to get host (for some reason this isn't working) - val (esUrl, extraArgs) = args match { - case h :: t => (h, t) - case _ => usageError - } - - run(esUrl, extraArgs) - } - - class EsClient(val url: String, val includeDenseVectorMappings: Boolean = true) extends ElasticSearchClient { - override def imagesCurrentAlias = esImagesAlias - override def shards = esShards - override def replicas = esReplicas - - @deprecated(message = "Should not be used in scripts", since = "2021-Jul-07") - lazy val imagesMigrationAlias: String = "Images_Migration" - - lazy val indexResult: Response[Map[String, GetIndexResponse]] = client.execute { - getIndex(imagesCurrentAlias) - }.await - - lazy val currentIndex: String = indexResult.status match { - case 200 => { - indexResult.result.keySet.toList match { - case head::Nil => head - case _ => throw new Exception(s"There should only be one index associated with alias before reindexing, " + - s"however there was: ${indexResult.result.keys}") - } - } - case _ => throw new Exception(s"Failed updating mapping: ${indexResult.error}") - } - - } - - def run(esUrl: String, args: List[String]): Unit - def usageError: Nothing -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/LoadFromS3Bucket.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/LoadFromS3Bucket.scala index 3ff86bd075c..e69de29bb2d 100644 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/LoadFromS3Bucket.scala +++ b/scripts/src/main/scala/com/gu/mediaservice/scripts/LoadFromS3Bucket.scala @@ -1,61 +0,0 @@ -package com.gu.mediaservice.scripts - -import org.apache.http.client.methods.HttpPost -import org.apache.http.entity.{ContentType, InputStreamEntity} -import org.apache.http.impl.client.HttpClients -import software.amazon.awssdk.auth.credentials.{AwsCredentialsProviderChain, InstanceProfileCredentialsProvider, ProfileCredentialsProvider} -import software.amazon.awssdk.core.ResponseInputStream -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.model.{GetObjectRequest, GetObjectResponse, ListObjectsV2Request} - -import scala.jdk.CollectionConverters._ - -object LoadFromS3Bucket { - - def apply(args: List[String]): Unit = { - - val (bucket, loaderEndpoint) = args match { - case List(b, l) => (b, l) - case _ => sys.error("Usage: LoadFromS3Bucket ") - } - - lazy val awsCredentials = AwsCredentialsProviderChain.of( - ProfileCredentialsProvider.create("media-service"), - InstanceProfileCredentialsProvider.create() - ) - - val client: S3Client = S3Client.builder() - .credentialsProvider(awsCredentials) - .build() - - val keys = client.listObjectsV2( - ListObjectsV2Request.builder().bucket(bucket).build() - ).contents().asScala.map(_.key) - - val httpClient = HttpClients.createDefault - - for (key <- keys) { - val getObjectRequest = GetObjectRequest.builder() - .bucket(bucket) - .key(key) - .build() - - val objStream: ResponseInputStream[GetObjectResponse] = client.getObject(getObjectRequest) - val length = objStream.response().contentLength() - - val postReq = new HttpPost(loaderEndpoint) - val entity = new InputStreamEntity(objStream, length, ContentType.DEFAULT_BINARY) - postReq.setEntity(entity) - - try { - httpClient.execute(postReq).close() - } finally { - objStream.close() - } - - println(s"Loaded image $key") - } - - } - -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/Main.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/Main.scala deleted file mode 100644 index f7db6dc4d4a..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/Main.scala +++ /dev/null @@ -1,25 +0,0 @@ -package com.gu.mediaservice.scripts - - -object Main extends App { - - args.toList match { - case "LoadFromS3Bucket" :: as => LoadFromS3Bucket(as) - case "Reindex" :: as => Reindex(as) - case "DownloadAllEsIds" :: as => DownloadAllEsIds(as) - case "GetMapping" :: as => GetMapping(as) - case "UpdateMapping" :: as => UpdateMapping(as) - case "GetSettings" :: as => GetSettings(as) - case "UpdateSettings" :: as => UpdateSettings(as) - case "ConvertConfig" :: as => ConvertConfig(as) - case "BucketMetadata" :: as => BucketMetadata(as) - case "DecodeComparator" :: as => DecodeComparator(as) - case "EnactS3Changes" :: as => EnactS3Changes(as) - case "BulkDeleteS3Files":: as => BulkDeleteS3Files(as) - case "EsMetadata" :: as => EsImageMetadata(as) - case "ProposeS3Changes" :: as => ProposeS3Changes(as) - case "BackfillEditLastModified" :: as => BackfillEditLastModified(as) - case a :: _ => sys.error(s"Unrecognised command: $a") - case Nil => sys.error("Usage: ") - } -} diff --git a/scripts/src/main/scala/com/gu/mediaservice/scripts/ProposeS3Changes.scala b/scripts/src/main/scala/com/gu/mediaservice/scripts/ProposeS3Changes.scala deleted file mode 100644 index f6a5a4d8e8d..00000000000 --- a/scripts/src/main/scala/com/gu/mediaservice/scripts/ProposeS3Changes.scala +++ /dev/null @@ -1,254 +0,0 @@ -package com.gu.mediaservice.scripts - -import com.gu.mediaservice.JsonDiff -import com.gu.mediaservice.lib.ImageStorageProps -import com.gu.mediaservice.lib.net.URI -import org.apache.commons.compress.compressors.bzip2.{BZip2CompressorInputStream, BZip2CompressorOutputStream} -import org.joda.time.DateTime -import play.api.libs.json.Json - -import java.io._ -import scala.collection.mutable -import scala.io.Source -import scala.language.postfixOps - -object ProposeS3Changes { - def apply(args: List[String]): Unit = { - args match { - case bucketMetadata :: esMetadata :: picdarCsv :: outputFile :: Nil => proposeS3Changes( - new File(bucketMetadata), - new File(esMetadata), - new File(picdarCsv), - new File(s"$outputFile.update.jsonl.bz2"), - new File(s"$outputFile.correct.jsonl.bz2"), - new File(s"$outputFile.esonlykeys.txt"), - new File(s"$outputFile.s3onlykeys.txt"), - new File(s"$outputFile.s3badkeys.txt"), - ) - case _ => throw new IllegalArgumentException("Usage: ProposeS3Changes ") - } - } - - def getBzipWriter(outputFile: File) = { - val fileOutputStream = new FileOutputStream(outputFile) - val compressOutputStream = new BZip2CompressorOutputStream(fileOutputStream) - val sw = new OutputStreamWriter(compressOutputStream) - new BufferedWriter(sw) - } - - def proposeS3Changes( - bucketMetadata: File, - esMetadata: File, - picdarCsv: File, - outputFileForJsonUpdate: File, - outputFileForJsonCorrect: File, - outputFileForESKeys: File, - outputFileForS3Keys: File, - outputFileForBadS3Keys: File) = { - val picdarData = readPicdarCsv(picdarCsv) - System.err.println(s"Completed reading ${picdarData.gridToPicdar.size} Picdar mappings") - - val s3Metadata = { - val (md, badKeys) = readS3Metadata(bucketMetadata) - System.err.println(s"Completed reading S3 metadata. ${md.size} records, ${badKeys.size} bad keys") - // write out the bad keys in this scope so they can then be GCd - val outputWriterForBadS3Keys = new FileWriter(outputFileForBadS3Keys) - try { - badKeys.foreach(k => outputWriterForBadS3Keys.append(s"$k\n")) - } finally { - outputWriterForBadS3Keys.close() - } - md - } - - var s3KeysNotYetSeenInEs = s3Metadata.keySet - var esKeysNotInS3 = Set.empty[String] - - System.err.println(s"Starting change proposals...") - val outputWriterForJsonUpdate = getBzipWriter(outputFileForJsonUpdate) - val outputWriterForJsonCorrect = getBzipWriter(outputFileForJsonCorrect) - val outputWriterForESKeys = new FileWriter(outputFileForESKeys) - val outputWriterForS3Keys = new FileWriter(outputFileForS3Keys) - try { - withSourceFromBzipFile(esMetadata){ source => - source - .getLines() - .flatMap(line => Json.fromJson[EsDocumentWithMetadata](Json.parse(line)).asOpt) - .zipWithIndex - .foreach { case (metadata, i) => - if (i % 10000 == 0) System.err.println(s"Processing ES metadata line $i") - val id = metadata.id - val maybeS3 = s3Metadata.get(id) - maybeS3 match { - case Some(s3Metadata) => - s3KeysNotYetSeenInEs -= id - val mergedMetadata = mergeMetadata(metadata, s3Metadata, picdarData) - if (mergedMetadata != mergeMetadata(metadata, mergedMetadata, picdarData)) { - System.err.println(s"Merged metadata for $id not idempotent") - } - if (mergedMetadata != s3Metadata) { - val jsS3 = Json.toJson(s3Metadata) - val jsMerged = Json.toJson(mergedMetadata) - val diff = JsonDiff.diff(jsS3, jsMerged) - outputWriterForJsonUpdate - .append(s"${Json.toJson(Json.obj( - "original" -> jsS3, - "proposed" -> jsMerged, - "diff" -> diff - )).toString()}\n") - } else { - outputWriterForJsonCorrect - .append(s"${Json.toJson(mergedMetadata).toString()}\n") - } - case None => - esKeysNotInS3 += id - } - } - } - - esKeysNotInS3.foreach(k => outputWriterForESKeys.append(s"$k\n")) - s3KeysNotYetSeenInEs.foreach(k => outputWriterForS3Keys.append(s"$k\n")) - } finally { - outputWriterForJsonUpdate.close() - outputWriterForJsonCorrect.close() - outputWriterForESKeys.close() - outputWriterForS3Keys.close() - } - } - - - def mergeMetadata(esMetadata: EsDocumentWithMetadata, s3Metadata: ObjectMetadata, picdarData: PicdarData): ObjectMetadata = { - /* If we can we should retain the legacy keys with _ in so that we don't have to touch the object */ - def bestKeyNameFor(dashVariant: String): String = { - val hasDashVariant = s3Metadata.metadata.contains(dashVariant) - // does this have underscore version of key? - val underscoreVariant = dashVariant.replace("-", "_") - val hasUnderscoreVariant = s3Metadata.metadata.contains(underscoreVariant) - if (hasDashVariant && hasUnderscoreVariant) { - System.err.println(s"Warning: both dash and underscore keys on ${s3Metadata.key}") - } - if (hasUnderscoreVariant) - underscoreVariant - else - dashVariant - } - - /* Find the "best" value, trying hard to detect values that haven't really changed despite encoding or format changes */ - def bestValue(maybeEsValue: Option[String], maybeS3Value: Option[String], isDate: Boolean = false): Option[String] = { - def isSame(esValue: String, s3Value: String): Boolean = { - val decodedS3 = URI.decode(s3Value) - if (!isDate) { - decodedS3 == esValue - } else { - val s3Date = DateTime.parse(decodedS3) - val esDate = DateTime.parse(esValue) - s3Date.equals(esDate) - } - } - (maybeEsValue, maybeS3Value) match { - case (None, s3Value) => s3Value - case (Some(esValue), Some(s3Value)) if isSame(esValue, s3Value) => Some(s3Value) - case (Some(esValue), _) => Some(URI.encode(esValue)) - } - } - - val filenameKey = bestKeyNameFor(ImageStorageProps.filenameMetadataKey) - val uploadedByKey = bestKeyNameFor(ImageStorageProps.uploadedByMetadataKey) - val uploadTimeKey = bestKeyNameFor(ImageStorageProps.uploadTimeMetadataKey) - - if (metadataEquivalent(esMetadata, s3Metadata)) { - s3Metadata - } else { - // filename: taken from ES if it exists, then from S3, otherwise empty - val fileName = bestValue(esMetadata.fileName, s3Metadata.metadata.get(filenameKey)) - .map(s => s.replaceAll(s" (${esMetadata.id})", "")) - // uploaded by: taken from ES if it exists, then from S3, otherwise empty - val uploadedBy = bestValue(esMetadata.uploadedBy, s3Metadata.metadata.get(uploadedByKey)) - // uploaded time: taken from ES if it exists, then from S3, otherwise empty - val uploadTime = bestValue(esMetadata.uploadTime, s3Metadata.metadata.get(uploadTimeKey), isDate = true) - - // Find ALL identifiers in elasticsearch (put "identifier!" on the front and make lowercase) - val esIdentifiers = esMetadata.identifiers - .map{ case (key, value ) => s"${ImageStorageProps.identifierMetadataKeyPrefix}$key".toLowerCase -> URI.encode(value)} - - // Find all OUR identifiers in S3 (must have "identifier!" on the front) - val s3Identifiers = s3Metadata.metadata - .filter{case (key, _) => key.startsWith(ImageStorageProps.identifierMetadataKeyPrefix)} - - val picdarIdEntry = picdarData.gridToPicdar.get(esMetadata.id).map(s"${ImageStorageProps.identifierMetadataKeyPrefix}picdarurn" ->) - - // Merge the two maps together with any picdar entry - val allIdentifierKeys = s3Identifiers.keySet ++ esIdentifiers.keySet - val identifiers = allIdentifierKeys.foldLeft(Map.empty[String, String]) { case (acc, key) => - acc ++ bestValue(esIdentifiers.get(key), s3Identifiers.get(key)).map(key ->) - } ++ picdarIdEntry - - ObjectMetadata( - key = s3Metadata.key, - lastModified = s3Metadata.lastModified, - metadata = identifiers - ++ fileName.map(fn => filenameKey -> fn) - ++ uploadedBy.map(ub => uploadedByKey -> ub) - ++ uploadTime.map(ut => uploadTimeKey -> ut) - ) - } - } - - def metadataEquivalent(metadata: EsDocumentWithMetadata, s3Metadata: ObjectMetadata): Boolean = { - metadata.uploadTime == s3Metadata.metadata.get(ImageStorageProps.uploadTimeMetadataKey) && - metadata.uploadedBy == s3Metadata.metadata.get(ImageStorageProps.uploadedByMetadataKey) && - metadata.fileName == s3Metadata.metadata.get(ImageStorageProps.filenameMetadataKey) && - metadata.identifiers.map{case (key, value) => - ImageStorageProps.identifierMetadataKeyPrefix + key.toLowerCase -> value - } == s3Metadata.metadata.filter{ case (key, _) => key.startsWith(ImageStorageProps.identifierMetadataKeyPrefix)} - } - - private val hex = """[0-9a-f]""" - private val GoodKey = s"""^($hex)/($hex)/($hex)/($hex)/($hex)/($hex)/(\\1\\2\\3\\4\\5\\6$hex{34})$$""".r - def readS3Metadata(bucketMetadata: File): (Map[String, ObjectMetadata], List[String]) = { - val (goodEntries, badValues) = withSourceFromBzipFile(bucketMetadata){ source => - source.getLines().zipWithIndex.foldLeft[(mutable.Builder[(String, ObjectMetadata), Map[String, ObjectMetadata]], List[String])] (Map.newBuilder[String, ObjectMetadata], Nil) { case ((goodEntries, badKeys), (line, i)) => - if (i % 10000 == 0) System.err.println(s"Loading index $i") - Json.fromJson[ObjectMetadata](Json.parse(line)).asOpt match { - case Some(objMd@ObjectMetadata(GoodKey(_, _, _, _, _, _, id), _, _)) => (goodEntries += (id -> objMd), badKeys) - case Some(ObjectMetadata(badKey, _, _)) => (goodEntries, badKey :: badKeys) - case None => (goodEntries, badKeys) - } - } - } - (goodEntries.result(), badValues) - } - - case class PicdarData(gridToPicdar: Map[String, String]) - - def readPicdarCsv(picdarCsvFile: File): PicdarData = { - val source = Source.fromFile(picdarCsvFile) - try { - val gridIdAndPicdarId = source.getLines().collect { - case line if line.contains(",") => - line.takeWhile(_ != ',') -> line.dropWhile(_ != ',').drop(1) - }.toList - val gridIds = gridIdAndPicdarId.map{ case (gridId, _) => gridId } - val picdarIds = gridIdAndPicdarId.map{ case (_, picdarId) => picdarId } - if (gridIds.length != gridIds.toSet.size) { - throw new IllegalArgumentException(s"Picdar CSV file contains duplicate IDs: ${gridIds.length} / ${gridIds.toSet.size} uniques") - } - val gridToPicdar = gridIdAndPicdarId.toMap - //val picdarToGrid = gridIdAndPicdarId.map(_.swap).toMap - PicdarData(gridToPicdar) - } finally { - source.close() - } - } - - private def withSourceFromBzipFile[T](file: File)(f: Source => T) = { - val fileInputStream = new FileInputStream(file) - val compressInputStream = new BZip2CompressorInputStream(fileInputStream) - val source = Source.fromInputStream(compressInputStream) - try { - f(source) - } finally { - source.close() - } - } -} diff --git a/scripts/usage-reindex/reindex-usages.sh b/scripts/usage-reindex/reindex-usages.sh deleted file mode 100644 index ff1a5355964..00000000000 --- a/scripts/usage-reindex/reindex-usages.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -isFastForwarding=true - -touch lastQueriedId.txt -lastQueriedId=$(cat lastQueriedId.txt) - -if [[ $lastQueriedId == "" ]] - then isFastForwarding=false -fi - -while read line; do - id=$(echo "$line" | cut -d "," -f 1) - path=$(echo "$line" | cut -d "," -f 2) - - if [[ $lastQueriedId == "$id" ]] - then isFastForwarding=false - continue - fi - - if [[ $isFastForwarding == 'true' ]] - then continue - fi - -# only gets to this if isFastForwarding is false - if curl "https://media-usage.test.dev-gutools.co.uk/usages/digital/content/$path/reindex" -H "X-Gu-Media-Key: $GRID_API_KEY" -f - then - echo "$id" > lastQueriedId.txt - echo "Reindexed id: $id, path: $path" - else - echo "$id,$path" >> failedIds.txt - echo "Failed on id: $id, path: $path" - fi - -done < codeContentIdAndPath.csv From 3708a07861699b900017446f7299a9a84c06f6c0 Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Tue, 25 Jun 2024 09:03:10 +0100 Subject: [PATCH 004/189] Delete GoodToGoCheck; not instance specific. --- thrall/app/ThrallComponents.scala | 12 +- .../app/lib/elasticsearch/GoodToGoCheck.scala | 126 -------------- .../lib/elasticsearch/GoodToGoCheckTest.scala | 158 ------------------ 3 files changed, 1 insertion(+), 295 deletions(-) delete mode 100644 thrall/test/lib/elasticsearch/GoodToGoCheckTest.scala diff --git a/thrall/app/ThrallComponents.scala b/thrall/app/ThrallComponents.scala index de8a3f08017..f2e77e17a95 100644 --- a/thrall/app/ThrallComponents.scala +++ b/thrall/app/ThrallComponents.scala @@ -31,17 +31,7 @@ class ThrallComponents(context: Context) extends GridComponents(context, new Thr val gridClient: GridClient = GridClient(config.services, config.services.thrallBaseUri)(wsClient) // before firing up anything to consume streams or say we are OK let's do the critical good to go check - private val goodToGoCheckResult = Await.ready(GoodToGoCheck.run(es), 30 seconds) - goodToGoCheckResult.value match { - case Some(Success(_)) => // all good - logger.info("Passed good to go") - case Some(Failure(exception)) => - logger.error("Failed good to go, aborting startup", exception) - throw exception - case other => - logger.warn(s"Result of good to go was $other, aborting as this result doesn't make sense") - throw new IllegalStateException("Good to go test didn't pass or throw exception") - } + // TODO restore a reduced non instance specific version of this private val goodToGoCheckResult = Await.ready(GoodToGoCheck.run(es), 30 seconds) val messageSender = new ThrallMessageSender(config.thrallKinesisStreamConfig) diff --git a/thrall/app/lib/elasticsearch/GoodToGoCheck.scala b/thrall/app/lib/elasticsearch/GoodToGoCheck.scala index 7df65a1d500..e69de29bb2d 100644 --- a/thrall/app/lib/elasticsearch/GoodToGoCheck.scala +++ b/thrall/app/lib/elasticsearch/GoodToGoCheck.scala @@ -1,126 +0,0 @@ -package lib.elasticsearch - -import com.gu.mediaservice.lib.aws.EC2MetadataUtils -import software.amazon.awssdk.imds.Ec2MetadataClient -import com.gu.mediaservice.lib.elasticsearch.{MappingTest, Running} -import com.gu.mediaservice.lib.logging.LogMarker -import com.gu.mediaservice.model.Image -import com.typesafe.scalalogging.StrictLogging -import org.joda.time.{DateTime, Period} - -import scala.concurrent.{ExecutionContext, Future} - -/** - * This implements a good to go check that validates when Thrall starts up that it can successfully write and read - * an image into the elasticsearch node. - */ -object GoodToGoCheck extends StrictLogging { - def run(es: ElasticSearch)(implicit ex: ExecutionContext): Future[Unit] = { - // make a test image with an ID that is unique to the node so that multiple nodes can run the check at once - val uniqueId = EC2MetadataUtils.getInstanceId.map(_.stripPrefix("i-")).getOrElse("abcdef") - val checkTime = DateTime.now - val image: Image = MappingTest.testImage.copy( - id = s"${MappingTest.testImage.id}$uniqueId", - lastModified = Some(checkTime) // use now as the last modified to aid cleanup of old test images - ) - val lastModified = image.lastModified.get - implicit val marker: LogMarker = image ++ Map("goToGo" -> true) - - val indexResult: Future[Unit] = - for { - // Perhaps a test image wasn't previously cleaned up... this might happen locally or if a thrall process - // is being cycled by systemd on an instance. Let's err on the side of caution and check and deal with it. - _ <- Future.successful(logger.info(s"Ensuring test image ${image.id} doesn't exist")) - checkAbsent <- es.getImage(image.id) - _ <- Future.successful(if (checkAbsent.nonEmpty) logger.info("Deleting prior test image")) - _ <- if (checkAbsent.nonEmpty) deleteTestImage(es, image.id) else Future.successful(()) - // now index and retrieve the test image - _ <- Future.successful(logger.info(s"Indexing test image ${image.id}")) - indexResult <- es.migrationAwareIndexImage(image.id, image, lastModified) - _ <- Future.successful(logger.info(s"Retrieving test image ${image.id}")) - maybeRetrieveResult <- es.getImage(image.id) - _ <- Future.successful(logger.info(s"Validating test image")) - validation <- { - // check retrieving it is the same object - note: we use a string check here as the DateTimes don't compare - // usefully and we override the lastModified to avoid TZ issues in BST - val retrievedImageSameAsIndexed = - approximatelyEqual(maybeRetrieveResult, Some(image)) - if (!retrievedImageSameAsIndexed) logger.warn(s"Image mismatch: \n$maybeRetrieveResult\n${Some(image)}") - - if (retrievedImageSameAsIndexed) { - Future.successful(()) - } else { - Future.failed(new IllegalStateException(s"Failed to validate insertion [Indexed 1 image; retrieved image matches indexed image: $retrievedImageSameAsIndexed]")) - } - } - // once that's all done, let's delete the image again so it's not lying around - _ <- Future.successful(logger.info(s"Deleting test image")) - _ <- deleteTestImage(es, image.id) - // clean up any images from over an hour ago that were not deleted - cleanedUpCount <- deleteOldTestImages(es, checkTime) - _ <- Future.successful(if (cleanedUpCount > 0) logger.info(s"Deleted $cleanedUpCount old test image(s)")) - } yield validation - - // return the future for the healthcheck - indexResult - } - - def deleteTestImage(es: ElasticSearch, imageId: String)(implicit ex: ExecutionContext, lm: LogMarker) = { - import com.sksamuel.elastic4s.ElasticDsl._ - // this manipulates the underlying ES API as the built in delete API guards against deleting images that are in use - Future.sequence((es.migrationStatus match { - case running: Running => List(es.imagesCurrentAlias, running.migrationIndexName) - case _ => List(es.imagesCurrentAlias) - }).map { indexName => - es.executeAndLog(deleteById(indexName, imageId), s"Deleting good to go test image $imageId") - }) - } - - def deleteOldTestImages(es: ElasticSearch, now: DateTime)(implicit ex: ExecutionContext, lm: LogMarker): Future[Long] = { - import com.sksamuel.elastic4s.ElasticDsl._ - // only act on images from some time ago so we don't delete image - val olderThan = now.minus(Period.minutes(10)) - val query = must( - rangeQuery("lastModified").lte(olderThan.getMillis), // older than this - termQuery("uploadedBy", MappingTest.testUploader) // and belonging to the uploader - ) - - val indicesToDeleteFrom = es.migrationStatus match { - case running: Running => List(es.imagesCurrentAlias, running.migrationIndexName) - case _ => List(es.imagesCurrentAlias) - } - - val deleteRequest = deleteIn(indicesToDeleteFrom).by(query).copy(waitForCompletion = Some(true)) - - - // this manipulates the underlying ES API as the built in delete API guards against deleting images that are in use - val eventualResult = es.executeAndLog(deleteRequest, s"Deleting any old test images uploaded by ${MappingTest.testImage.uploadedBy}") - - eventualResult.map(_.result match { - case Left(deleteResponse) => deleteResponse.deleted - // The Right (createdTask) response doesn't make sense - we have wait for completion enabled, but elastic4s forces us to still consider this case - case Right(createdTask) => throw new IllegalArgumentException("Request to delete_by_query returned an asynchronous task, despite wait_for_completion requested") - }) - } - - /** This function computes an approximate equality for some objects which is fuzzy when it comes to Joda DateTimes - * and will say that two objects are equal even when they contain DateTimes within them that are the same point - * in time but expressed in two different ways (i.e. two different time zones) - * @param a The first object to compare - * @param b A second object to compare - * @return true if the objects are equal (aside from chronology/timezone in DateTimes) - */ - def approximatelyEqual(a: Any, b: Any): Boolean = { - (a, b) match { - case (aTrav: Iterable[_], bTrav: Iterable[_]) => - aTrav.zip(bTrav) - .forall { case (aVal, bVal) => approximatelyEqual(aVal, bVal) } - case (aProduct: Product, bProduct: Product) => - aProduct.productIterator - .zip(bProduct.productIterator) - .forall { case (aVal, bVal) => approximatelyEqual(aVal, bVal) } - case (aDateTime: DateTime, bDateTime: DateTime) => aDateTime.getMillis == bDateTime.getMillis - case (aOther, bOther) => aOther == bOther - } - } -} diff --git a/thrall/test/lib/elasticsearch/GoodToGoCheckTest.scala b/thrall/test/lib/elasticsearch/GoodToGoCheckTest.scala deleted file mode 100644 index 2ef2ab00514..00000000000 --- a/thrall/test/lib/elasticsearch/GoodToGoCheckTest.scala +++ /dev/null @@ -1,158 +0,0 @@ -package lib.elasticsearch - -import com.gu.mediaservice.lib.elasticsearch.MappingTest -import com.gu.mediaservice.lib.logging.LogMarker -import com.sksamuel.elastic4s.ElasticDsl._ -import org.joda.time.{DateTime, DateTimeZone, Period} - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future - -class GoodToGoCheckTest extends ElasticSearchTestBase { - implicit val lm: LogMarker = new LogMarker{ - override def markerContents: Map[String, Any] = Map.empty - } - - "GoodToGoCheck" - { - "the good to go check passes when run against docker" in { - GoodToGoCheck.run(ES).futureValue - } - "deleteOldTestImages" - { - val now = new DateTime(2021, 3, 3, 3, 0) - val old = now.minus(Period.minutes(15)) - val testIds = List("first", "second", "third") - "should delete old test images" in { - // insert a few 'old' test images - Future.sequence(testIds.map { id => - ES.migrationAwareIndexImage(id, MappingTest.testImage.copy(id = id), old) - }).futureValue - - // wait for them to appear in ES - testIds.foreach { id => - eventually(timeout(fiveSeconds), interval(oneHundredMilliseconds))(reloadedImage(id).map(_.id) shouldBe Some(id)) - } - - eventually { - ES.client.execute(count(ES.initialImagesIndex)) - .futureValue.result.count shouldBe 3 - } - - // now delete them - GoodToGoCheck.deleteOldTestImages(ES, now).futureValue shouldBe 3 - - // wait for them to disappear from ES - testIds.foreach { id => - eventually(timeout(fiveSeconds), interval(oneHundredMilliseconds))(reloadedImage(id).map(_.id) shouldBe None) - } - } - - "should not delete recent test images" in { - val notVeryOld = now.minus(Period.minutes(5)) - // insert one normal and a few others with an alternative uploader - val eventualInserts: List[Future[ElasticSearchUpdateResponse]] = - ES.migrationAwareIndexImage("test", MappingTest.testImage.copy(id = "test"), old) :: - testIds.map { id => - ES.migrationAwareIndexImage(id, MappingTest.testImage.copy(id = id), notVeryOld) - } - Future.sequence(eventualInserts).futureValue - // ensure they are all in ES - eventually(reloadedImage("test").map(_.id) shouldBe Some("test")) - testIds.foreach { id => - eventually(reloadedImage(id).map(_.id) shouldBe Some(id)) - } - eventually { - ES.client.execute(count(ES.initialImagesIndex)) - .futureValue.result.count shouldBe 4 - } - - // now run a delete old test images to show that our images remain - GoodToGoCheck.deleteOldTestImages(ES, now).futureValue shouldBe 1 - // ensure the test image is gone - eventually(reloadedImage("test").map(_.id) shouldBe None) - // ensure the other images still exist - testIds.foreach { id => - reloadedImage(id).map(_.id) shouldBe Some(id) - } - } - - "should not delete images without the defined test uploader" in { - val eventualInserts: List[Future[ElasticSearchUpdateResponse]] = - ES.migrationAwareIndexImage("old", MappingTest.testImage.copy(id = "old"), old) :: - testIds.map { id => - ES.migrationAwareIndexImage(id, MappingTest.testImage.copy(id = id, uploadedBy = "joe-bloggs"), old) - } - val eventualResponses = Future.sequence(eventualInserts).futureValue - eventually(reloadedImage("old").map(_.id) shouldBe Some("old")) - testIds.foreach { id => - eventually(reloadedImage(id).map(_.id) shouldBe Some(id)) - } - eventually { - ES.client.execute(count(ES.initialImagesIndex)) - .futureValue.result.count shouldBe 4 - } - - // now run a delete old test images to show that our normal images remain - GoodToGoCheck.deleteOldTestImages(ES, now).futureValue shouldBe 1 - // now check that the test image has gone - eventually(reloadedImage("old").map(_.id) shouldBe None) - // confirm that our other images are still present - testIds.foreach { id => - reloadedImage(id).map(_.id) shouldBe Some(id) - } - } - } - "approximatelyEqual" - { - val lastModified = MappingTest.testImage.lastModified.get - val newLastModified = lastModified.withZone(DateTimeZone.forOffsetHours(3)) - val differentLastModified = lastModified.plus(1) - "lastModified dates don't match but have same millis" in { - lastModified.getMillis shouldBe newLastModified.getMillis - lastModified shouldNot be(newLastModified) - } - "return true for objects that are identical" in { - GoodToGoCheck.approximatelyEqual(MappingTest.testImage, MappingTest.testImage) shouldBe true - } - "return false for objects that have a different nested field" in { - GoodToGoCheck.approximatelyEqual( - MappingTest.testImage, - MappingTest.testImage.copy(metadata = MappingTest.testImage.metadata.copy(title = Some("banana"))) - ) shouldBe false - } - "return true for objects that have a similar date time" in { - lastModified.getMillis shouldBe newLastModified.getMillis - lastModified shouldNot be(newLastModified) - GoodToGoCheck.approximatelyEqual( - MappingTest.testImage, - MappingTest.testImage.copy(lastModified = Some(newLastModified)) - ) shouldBe true - } - "works for option of date time" - { - case class OptionOfDateTime(field: Option[DateTime]) - "that is the same" in { - val first = OptionOfDateTime(Some(lastModified)) - val second = OptionOfDateTime(Some(newLastModified)) - GoodToGoCheck.approximatelyEqual(first, second) shouldBe true - } - "that is actually different" in { - val first = OptionOfDateTime(Some(lastModified)) - val second = OptionOfDateTime(Some(differentLastModified)) - GoodToGoCheck.approximatelyEqual(first, second) shouldBe false - } - } - - "works for list of date time" - { - case class ListOfDateTime(field: List[DateTime]) - "that is the same" in { - val first = ListOfDateTime(List(lastModified)) - val second = ListOfDateTime(List(newLastModified)) - GoodToGoCheck.approximatelyEqual(first, second) shouldBe true - } - "works for list of date time that is actually different" in { - val first = ListOfDateTime(List(lastModified)) - val second = ListOfDateTime(List(differentLastModified)) - GoodToGoCheck.approximatelyEqual(first, second) shouldBe false - } - } - } - } -} From c7455952cf14c9c7bb8b03bd701f8c1df536b361 Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Thu, 29 Aug 2024 14:38:11 +0100 Subject: [PATCH 005/189] Introduce an Instance model class and a way to infer it from a request. --- .../lib/config/InstanceForRequest.scala | 13 +++++++++++++ .../scala/com/gu/mediaservice/model/Instance.scala | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 common-lib/src/main/scala/com/gu/mediaservice/lib/config/InstanceForRequest.scala create mode 100644 common-lib/src/main/scala/com/gu/mediaservice/model/Instance.scala diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/config/InstanceForRequest.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/config/InstanceForRequest.scala new file mode 100644 index 00000000000..45da79a9c95 --- /dev/null +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/config/InstanceForRequest.scala @@ -0,0 +1,13 @@ +package com.gu.mediaservice.lib.config + +import com.gu.mediaservice.model.Instance +import play.api.mvc.RequestHeader + +trait InstanceForRequest { + + def instanceOf(request: RequestHeader): Instance = { + // TODO some sort of filter supplied attribute + Instance(request.host.split("\\.").head) + } + +} diff --git a/common-lib/src/main/scala/com/gu/mediaservice/model/Instance.scala b/common-lib/src/main/scala/com/gu/mediaservice/model/Instance.scala new file mode 100644 index 00000000000..958f3ea64fa --- /dev/null +++ b/common-lib/src/main/scala/com/gu/mediaservice/model/Instance.scala @@ -0,0 +1,3 @@ +package com.gu.mediaservice.model + +case class Instance(id: String) From c4a4fa322b720a0bc71ca59b4f844bdcb8b9eddc Mon Sep 17 00:00:00 2001 From: Tony McCrae Date: Sat, 23 Nov 2024 11:56:26 +0000 Subject: [PATCH 006/189] Multi tenant vhosts - Redefine Service URLs to be a function of instance. All code which refers to a service URL needs to known which instance it is running as. Work through the usages. Switch service names to instance aware instance.domain vhost names. auth, kahuna and thrall move to vhost url. Stops at extends Panda. We are going to replace Panda though. loader, projection, usages, collections and leases move to vhost urls. XXX Forces migration to be instance aware to project images! cropper and metadata-editor are vhost based. imgproxy is vhost based. KahunaController will need to evaluate the service URLs to pass to the client on the fly. This is a different concern to the config class which provides the function for doing this. Catch all .rootUri usages in s strings in media-api. Media API will probably render contextual links now. s string substitutions allow alot of unevaluated functions to be rendered toString! --- auth/app/auth/AuthConfig.scala | 5 +- auth/app/auth/AuthController.scala | 20 ++-- .../controllers/CollectionsController.scala | 61 ++++++------ collections/app/lib/CollectionsConfig.scala | 3 +- .../com/gu/mediaservice/GridClient.scala | 62 ++++++------ .../com/gu/mediaservice/ImageDataMerger.scala | 2 +- .../mediaservice/lib/auth/ApiAccessor.scala | 6 +- .../gu/mediaservice/lib/config/Services.scala | 66 +++++++------ .../com/gu/mediaservice/model/Edits.scala | 31 +++--- .../app/controllers/CropperController.scala | 27 +++--- cropper/app/lib/CropperConfig.scala | 5 +- .../controllers/ImageLoaderController.scala | 60 +++++++----- .../controllers/UploadStatusController.scala | 7 +- image-loader/app/lib/ImageLoaderConfig.scala | 6 +- image-loader/app/model/Projector.scala | 6 +- .../app/model/QuarantineUploader.scala | 6 +- image-loader/app/model/Uploader.scala | 28 +++--- .../test/scala/model/ProjectorTest.scala | 2 + kahuna/app/KahunaComponents.scala | 23 ++--- kahuna/app/controllers/KahunaController.scala | 22 +++-- kahuna/app/lib/KahunaClientServiceUrls.scala | 3 + kahuna/app/lib/KahunaConfig.scala | 7 +- kahuna/app/views/main.scala.html | 10 +- .../controllers/MediaLeaseController.scala | 22 +++-- leases/app/lib/LeasesConfig.scala | 17 ++-- media-api/app/controllers/MediaApi.scala | 97 +++++++++++-------- media-api/app/lib/ImageResponse.scala | 94 +++++++++--------- media-api/app/lib/MediaApiConfig.scala | 21 ++-- .../app/controllers/EditsApi.scala | 33 ++++--- .../app/controllers/EditsController.scala | 23 +++-- .../controllers/SyndicationController.scala | 5 +- metadata-editor/app/lib/EditsConfig.scala | 8 +- .../lib/auth/Authentication.scala | 21 ++-- .../mediaservice/lib/auth/Authorisation.scala | 10 +- .../BaseControllerWithLoginRedirects.scala | 8 +- .../LocalAuthenticationProvider.scala | 14 ++- .../auth/PandaAuthenticationProvider.scala | 13 +-- .../lib/play/GridComponents.scala | 8 +- thrall/app/ThrallComponents.scala | 2 +- thrall/app/controllers/ThrallController.scala | 33 ++++--- .../app/lib/MigrationSourceWithSender.scala | 5 +- .../test/lib/ThrallStreamProcessorTest.scala | 7 +- usage/app/controllers/UsageApi.scala | 39 +++++--- usage/app/lib/UsageConfig.scala | 5 +- 44 files changed, 540 insertions(+), 413 deletions(-) create mode 100644 kahuna/app/lib/KahunaClientServiceUrls.scala diff --git a/auth/app/auth/AuthConfig.scala b/auth/app/auth/AuthConfig.scala index c16150cf8ad..be94f7d6901 100644 --- a/auth/app/auth/AuthConfig.scala +++ b/auth/app/auth/AuthConfig.scala @@ -1,8 +1,9 @@ package auth import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources} +import com.gu.mediaservice.model.Instance class AuthConfig(resources: GridConfigResources) extends CommonConfig(resources) { - val rootUri: String = services.authBaseUri - val mediaApiUri: String = services.apiBaseUri + val rootUri: Instance => String = services.authBaseUri + val mediaApiUri: Instance => String = services.apiBaseUri } diff --git a/auth/app/auth/AuthController.scala b/auth/app/auth/AuthController.scala index de4c8597b89..0bffa12dac9 100644 --- a/auth/app/auth/AuthController.scala +++ b/auth/app/auth/AuthController.scala @@ -6,9 +6,10 @@ import com.gu.mediaservice.lib.auth.Authentication.{InnerServicePrincipal, Machi import com.gu.mediaservice.lib.auth.Permissions.{DeleteImage, ShowPaid, UploadImages} import com.gu.mediaservice.lib.auth.provider.AuthenticationProviders import com.gu.mediaservice.lib.auth.{Authentication, Authorisation, Internal} +import com.gu.mediaservice.lib.config.InstanceForRequest import com.gu.mediaservice.lib.guardian.auth.PandaAuthenticationProvider import play.api.libs.json.Json -import play.api.mvc.{BaseController, ControllerComponents, Result} +import play.api.mvc.{AnyContent, BaseController, ControllerComponents, Request, Result} import java.net.URI import java.time.Instant @@ -19,15 +20,16 @@ class AuthController(auth: Authentication, providers: AuthenticationProviders, v override val controllerComponents: ControllerComponents, authorisation: Authorisation)(implicit ec: ExecutionContext) extends BaseController - with ArgoHelpers { + with ArgoHelpers with InstanceForRequest { - val indexResponse = { + def indexResponse()(r: Request[AnyContent]) = { + val instance = instanceOf(r) val indexData = Map("description" -> "This is the Auth API") val indexLinks = List( - Link("root", config.mediaApiUri), - Link("login", config.services.loginUriTemplate), - Link("ui:logout", s"${config.rootUri}/logout"), - Link("session", s"${config.rootUri}/session") + Link("root", config.mediaApiUri(instance)), + Link("login", config.services.loginUriTemplate(instance)), + Link("ui:logout", s"${config.rootUri(instance)}/logout"), + Link("session", s"${config.rootUri(instance)}/session") ) respond(indexData, indexLinks) } @@ -45,7 +47,9 @@ class AuthController(auth: Authentication, providers: AuthenticationProviders, v } } - def index = auth { indexResponse } + def index = auth { request => + indexResponse()(request) + } def session = auth { request => val showPaid = authorisation.hasPermissionTo(ShowPaid)(request.user) diff --git a/collections/app/controllers/CollectionsController.scala b/collections/app/controllers/CollectionsController.scala index 1a3a4c8986e..58ff3118459 100644 --- a/collections/app/controllers/CollectionsController.scala +++ b/collections/app/controllers/CollectionsController.scala @@ -6,16 +6,18 @@ import com.gu.mediaservice.lib.argo.model.{EmbeddedEntity, Link} import com.gu.mediaservice.lib.auth.Authentication import com.gu.mediaservice.lib.auth.Authentication.getIdentity import com.gu.mediaservice.lib.collections.CollectionsManager -import com.gu.mediaservice.model.{ActionData, Collection} +import com.gu.mediaservice.lib.config.InstanceForRequest +import com.gu.mediaservice.model.{ActionData, Collection, Instance} import lib.CollectionsConfig import model.Node import org.joda.time.DateTime import play.api.libs.functional.syntax._ import play.api.libs.json._ -import play.api.mvc.{BaseController, ControllerComponents} +import play.api.mvc.{BaseController, ControllerComponents, Request} import store.{CollectionsStore, CollectionsStoreError} import com.gu.mediaservice.lib.net.{URI => UriOps} import software.amazon.awssdk.services.dynamodb.model.AttributeValue +import com.gu.mediaservice.lib.net.{URI => UriOps} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future @@ -31,31 +33,31 @@ object AppIndex { } class CollectionsController(authenticated: Authentication, config: CollectionsConfig, store: CollectionsStore, - val controllerComponents: ControllerComponents) extends BaseController with ArgoHelpers { + val controllerComponents: ControllerComponents) extends BaseController with ArgoHelpers with InstanceForRequest { import CollectionsManager.{getCssColour, isValidPathBit, pathToUri, uriToPath} // Stupid name clash between Argo and Play import com.gu.mediaservice.lib.argo.model.{Action => ArgoAction} - def uri(u: String) = URI.create(u) - val collectionUri = uri(s"${config.rootUri}/collections") - def collectionUri(p: List[String] = Nil) = { + private def uri(u: String) = URI.create(u) + private def collectionUri()(implicit instance: Instance) = uri(s"${config.rootUri(instance)}/collections") + private def collectionUri(p: List[String] = Nil)(implicit instance: Instance) = { val path = if(p.nonEmpty) s"/${pathToUri(p)}" else "" - uri(s"${config.rootUri}/collections$path") + uri(s"${config.rootUri(instance)}/collections$path") } - val appIndex = AppIndex("media-collections", "The one stop shop for collections") - val indexLinks = List(Link("collections", collectionUri.toString)) + private val appIndex = AppIndex("media-collections", "The one stop shop for collections") + private def indexLinks()(implicit instance: Instance) = List(Link("collections", collectionUri().toString)) - def getNodeAction(n: Node[Collection]): Option[Link] = Some(Link("collection", collectionUri(n.fullPath).toString)) - def addChildAction(pathId: List[String] = Nil): Option[ArgoAction] = Some(ArgoAction("add-child", collectionUri(pathId), "POST")) - def addChildAction(n: Node[Collection]): Option[ArgoAction] = addChildAction(n.fullPath) - def removeNodeAction(n: Node[Collection]): Option[ArgoAction] = if (n.children.nonEmpty) None else Some( + private def getNodeAction(n: Node[Collection])(implicit instance: Instance): Option[Link] = Some(Link("collection", collectionUri(n.fullPath).toString)) + private def addChildAction(pathId: List[String] = Nil)(implicit instance: Instance): Option[ArgoAction] = Some(ArgoAction("add-child", collectionUri(pathId), "POST")) + private def addChildAction(n: Node[Collection])(implicit instance: Instance): Option[ArgoAction] = addChildAction(n.fullPath) + private def removeNodeAction(n: Node[Collection])(implicit instance: Instance): Option[ArgoAction] = if (n.children.nonEmpty) None else Some( ArgoAction("remove", collectionUri(n.fullPath), "DELETE") ) def index = authenticated { req => - respond(appIndex, links = indexLinks) + respond(appIndex, links = indexLinks()(instanceOf(req))) } def collectionNotFound(path: String) = @@ -70,11 +72,11 @@ class CollectionsController(authenticated: Authentication, config: CollectionsCo def storeError(message: String) = respondError(InternalServerError, "collection-store-error", message) - def getActions(n: Node[Collection]): List[ArgoAction] = { + def getActions(n: Node[Collection])(implicit instance: Instance): List[ArgoAction] = { List(addChildAction(n), removeNodeAction(n)).flatten } - def getLinks(n: Node[Collection]): List[Link] = { + private def getLinks(n: Node[Collection])(implicit instance: Instance): List[Link] = { List(getNodeAction(n)).flatten } @@ -107,7 +109,8 @@ class CollectionsController(authenticated: Authentication, config: CollectionsCo (collection) => collection.description) } - def getCollection(collectionPathId: String) = authenticated.async { + def getCollection(collectionPathId: String) = authenticated.async { request => + implicit val instance: Instance = instanceOf(request) store.get(uriToPath(collectionPathId)).map { case Some(collection) => val node = Node(collection.path.last, Nil, collection.path, collection.path, Some(collection)) @@ -120,6 +123,17 @@ class CollectionsController(authenticated: Authentication, config: CollectionsCo } def getCollections = authenticated.async { req => + implicit val instance: Instance = instanceOf(req) + implicit def asArgo: Writes[Node[Collection]] = ( + (__ \ "basename").write[String] ~ + (__ \ "children").lazyWrite[CollectionsEntity](Writes[CollectionsEntity] + // This is so we don't have to rewrite the Write[Seq[T]] + (seq => Json.toJson(seq))).contramap(collectionsEntity(_: List[Node[Collection]])) ~ + (__ \ "fullPath").write[List[String]] ~ + (__ \ "data").writeNullable[Collection] ~ + (__ \ "cssColour").writeNullable[String] + )(node => (node.basename, node.children, node.fullPath, node.data, getCssColour(node.fullPath))) + allCollections.map { tree => respond( Json.toJson(tree)(asArgo), @@ -134,6 +148,7 @@ class CollectionsController(authenticated: Authentication, config: CollectionsCo def addChildToRoot = addChildTo(None) def addChildToCollection(collectionPathId: String) = addChildTo(Some(collectionPathId)) def addChildTo(collectionPathId: Option[String]) = authenticated.async(parse.json) { req => + implicit val instance: Instance = instanceOf(req) (req.body \ "data").asOpt[String] map { child => if (isValidPathBit(child)) { val path = collectionPathId.map(uriToPath).getOrElse(Nil) :+ child @@ -195,18 +210,8 @@ class CollectionsController(authenticated: Authentication, config: CollectionsCo )(node => (node.basename, node.children, node.fullPath, node.data)) type CollectionsEntity = Seq[EmbeddedEntity[Node[Collection]]] - implicit def asArgo: Writes[Node[Collection]] = ( - (__ \ "basename").write[String] ~ - (__ \ "children").lazyWrite[CollectionsEntity](Writes[CollectionsEntity] - // This is so we don't have to rewrite the Write[Seq[T]] - (seq => Json.toJson(seq))).contramap(collectionsEntity) ~ - (__ \ "fullPath").write[List[String]] ~ - (__ \ "data").writeNullable[Collection] ~ - (__ \ "cssColour").writeNullable[String] - )(node => (node.basename, node.children, node.fullPath, node.data, getCssColour(node.fullPath))) - - def collectionsEntity(nodes: List[Node[Collection]]): CollectionsEntity = { + private def collectionsEntity(nodes: List[Node[Collection]])(implicit instance: Instance): CollectionsEntity = { nodes.map(n => EmbeddedEntity(collectionUri(n.fullPath), Some(n), links = getLinks(n), actions = getActions(n))) } diff --git a/collections/app/lib/CollectionsConfig.scala b/collections/app/lib/CollectionsConfig.scala index 9570409bc2d..20dcb8ff52e 100644 --- a/collections/app/lib/CollectionsConfig.scala +++ b/collections/app/lib/CollectionsConfig.scala @@ -1,11 +1,12 @@ package lib import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources} +import com.gu.mediaservice.model.Instance class CollectionsConfig(resources: GridConfigResources) extends CommonConfig(resources) { val collectionsTable = string("dynamo.table.collections") val imageCollectionsTable = string("dynamo.table.imageCollections") - val rootUri = services.collectionsBaseUri + val rootUri: Instance => String = services.collectionsBaseUri } diff --git a/common-lib/src/main/scala/com/gu/mediaservice/GridClient.scala b/common-lib/src/main/scala/com/gu/mediaservice/GridClient.scala index c3207891d53..fda9049d1d9 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/GridClient.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/GridClient.scala @@ -3,7 +3,7 @@ package com.gu.mediaservice import java.net.URL import com.gu.mediaservice.GridClient.{Error, Found, NotFound, Response} import com.gu.mediaservice.lib.config.Services -import com.gu.mediaservice.model.{Collection, Crop, Edits, Image, ImageMetadata, ImageStatusRecord, SourceImage, SyndicationRights} +import com.gu.mediaservice.model.{Collection, Crop, Edits, Image, ImageMetadata, ImageStatusRecord, Instance, SourceImage, SyndicationRights} import com.gu.mediaservice.model.leases.LeasesByMedia import com.gu.mediaservice.model.usage.Usage import com.typesafe.scalalogging.LazyLogging @@ -38,7 +38,7 @@ object ClientResponse { case class ClientErrorMessages(errorMessage: String, downstreamErrorMessage: String) object GridClient extends LazyLogging { - def apply(services: Services, originUri: String)(implicit wsClient: WSClient): GridClient = + def apply(services: Services, originUri: Instance => String)(implicit wsClient: WSClient): GridClient = new GridClient(services, originUri) sealed trait Response { @@ -96,7 +96,7 @@ object GridClient extends LazyLogging { } -class GridClient(services: Services, originDomain: String)(implicit wsClient: WSClient) extends LazyLogging { +class GridClient(services: Services, originDomain: Instance => String)(implicit wsClient: WSClient) extends LazyLogging { /* * `requestTimeout` will set the max duration of the request before timing out. You may also want to increase the @@ -134,8 +134,8 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getProjectionDiff(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[Option[JsValue]] = { - val url = new URL(s"${services.apiBaseUri}/images/$mediaId/projection/diff") + def getProjectionDiff(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[Option[JsValue]] = { + val url = new URL(s"${services.apiBaseUri(instance)}/images/$mediaId/projection/diff") makeGetRequestAsync(url, authFn, requestTimeout = Some(120.seconds)).map { case Found(json, _) => Some(json) case NotFound(_, _) => None @@ -144,8 +144,8 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } def getImageLoaderProjection(mediaId: String, authFn: WSRequest => WSRequest) - (implicit ec: ExecutionContext): Future[Option[Image]] = { - getImageLoaderProjection(mediaId, services.projectionBaseUri, authFn) + (implicit ec: ExecutionContext, instance: Instance): Future[Option[Image]] = { + getImageLoaderProjection(mediaId, services.projectionBaseUri(instance), authFn) } def getImageLoaderProjection(mediaId: String, imageLoaderEndpoint: String, authFn: WSRequest => WSRequest) @@ -159,9 +159,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getLeases(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[LeasesByMedia] = { + def getLeases(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[LeasesByMedia] = { logger.info("attempt to get leases") - val url = new URL(s"${services.leasesBaseUri}/leases/media/$mediaId") + val url = new URL(s"${services.leasesBaseUri(instance)}/leases/media/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => (json \ "data").as[LeasesByMedia] case NotFound(_, _) => LeasesByMedia.empty @@ -169,9 +169,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getCollections(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[List[Collection]] = { + def getCollections(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[List[Collection]] = { logger.info("attempt to get collections") - val url = new URL(s"${services.collectionsBaseUri}/images/$mediaId") + val url = new URL(s"${services.collectionsBaseUri(instance)}/images/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => (json \ "data").as[List[Collection]] case NotFound(_, _) => Nil @@ -179,9 +179,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getEdits(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[Option[Edits]] = { + def getEdits(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[Option[Edits]] = { logger.info("attempt to get edits") - val url = new URL(s"${services.metadataBaseUri}/edits/$mediaId") + val url = new URL(s"${services.metadataBaseUri(instance)}/edits/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => Some((json \ "data").as[Edits]) case NotFound(_, _) => None @@ -189,9 +189,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getSoftDeletedMetadata(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[Option[ImageStatusRecord]] = { + def getSoftDeletedMetadata(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[Option[ImageStatusRecord]] = { logger.info("attempt to get soft deleted metadata") - val url = new URL(s"${services.apiBaseUri}/images/$mediaId/softDeletedMetadata") + val url = new URL(s"${services.apiBaseUri(instance)}/images/$mediaId/softDeletedMetadata") makeGetRequestAsync(url, authFn) map { case Found(json, _) => Some((json \ "data").as[ImageStatusRecord]) case NotFound(_, _) => None @@ -199,9 +199,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getUploadedBy(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[Option[String]] = { + def getUploadedBy(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[Option[String]] = { logger.info("attempt to get uploadedBy") - val url = new URL(s"${services.apiBaseUri}/images/$mediaId/uploadedBy") + val url = new URL(s"${services.apiBaseUri(instance)}/images/$mediaId/uploadedBy") makeGetRequestAsync(url, authFn) map { case Found(json, _) => Some((json \ "data").as[String]) case NotFound(_, _) => None @@ -209,9 +209,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getCrops(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[List[Crop]] = { + def getCrops(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[List[Crop]] = { logger.info("attempt to get crops") - val url = new URL(s"${services.cropperBaseUri}/crops/$mediaId") + val url = new URL(s"${services.cropperBaseUri(instance)}/crops/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => (json \ "data").as[List[Crop]] case NotFound(_, _) => Nil @@ -219,7 +219,7 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getUsages(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[List[Usage]] = { + def getUsages(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[List[Usage]] = { logger.info("attempt to get usages") def unpackUsagesFromEntityResponse(resBody: JsValue): List[JsValue] = { @@ -227,7 +227,7 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS .map(entity => (entity.as[JsObject] \ "data").as[JsValue]).toList } - val url = new URL(s"${services.usageBaseUri}/usages/media/$mediaId") + val url = new URL(s"${services.usageBaseUri(instance)}/usages/media/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => unpackUsagesFromEntityResponse(json).map(_.as[Usage]) case NotFound(_, _) => Nil @@ -235,9 +235,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getSourceImage(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[SourceImage] = { + def getSourceImage(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[SourceImage] = { logger.info("attempt to get image") - val url = new URL(s"${services.apiBaseUri}/images/$mediaId") + val url = new URL(s"${services.apiBaseUri(instance)}/images/$mediaId") makeGetRequestAsync(url, authFn, queryStringParameters = Some(Seq("include" -> "fileMetadata"))) map { case Found(json, _) => json.as[SourceImage] case nf@NotFound(_, _) => Error(nf.status, url, nf.underlying).logErrorAndThrowException() @@ -245,9 +245,9 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getMetadata(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext): Future[ImageMetadata] = { + def getMetadata(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance): Future[ImageMetadata] = { logger.info("attempt to get metadata") - val url = new URL(s"${services.apiBaseUri}/images/$mediaId") + val url = new URL(s"${services.apiBaseUri(instance)}/images/$mediaId") makeGetRequestAsync(url, authFn) map { case Found(json, _) => (json \ "data" \ "metadata").as[ImageMetadata] case nf@NotFound(_, _) => Error(nf.status, url, nf.underlying).logErrorAndThrowException() @@ -255,8 +255,8 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def getSyndicationRights(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext) = { - val url = new URL(s"${services.metadataBaseUri}/metadata/$mediaId/syndication") + def getSyndicationRights(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance) = { + val url = new URL(s"${services.metadataBaseUri(instance)}/metadata/$mediaId/syndication") makeGetRequestAsync(url, authFn) map { case Found(json, _) => Some((json \ "data").as[SyndicationRights]) case _: NotFound => None @@ -264,15 +264,15 @@ class GridClient(services: Services, originDomain: String)(implicit wsClient: WS } } - def postUsage(usageType: String, data: JsObject, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext) = { - val url = new URL(s"${services.usageBaseUri}/usages/$usageType") + def postUsage(usageType: String, data: JsObject, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance) = { + val url = new URL(s"${services.usageBaseUri(instance)}/usages/$usageType") val request: WSRequest = wsClient.url(url.toString) val authorisedRequest = authFn(request) authorisedRequest.post(Json.obj("data" -> data)).map { response => validateResponse(response, url)} } - def putArchived(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext) = { - val url = new URL(s"${services.metadataBaseUri}/metadata/$mediaId/archived") + def putArchived(mediaId: String, authFn: WSRequest => WSRequest)(implicit ec: ExecutionContext, instance: Instance) = { + val url = new URL(s"${services.metadataBaseUri(instance)}/metadata/$mediaId/archived") val request = authFn(wsClient.url(url.toString)) request.put(Json.obj("data" -> JsTrue)).map { response => validateResponse(response, url)} } diff --git a/common-lib/src/main/scala/com/gu/mediaservice/ImageDataMerger.scala b/common-lib/src/main/scala/com/gu/mediaservice/ImageDataMerger.scala index 463c94500ea..acefd725528 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/ImageDataMerger.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/ImageDataMerger.scala @@ -8,7 +8,7 @@ import play.api.libs.ws.WSRequest import scala.concurrent.{ExecutionContext, Future} object ImageDataMerger extends GridLogging { - def aggregate(image: Image, gridClient: GridClient, authFunction: WSRequest => WSRequest)(implicit ec: ExecutionContext, logMarker: LogMarker): Future[Image] = { + def aggregate(image: Image, gridClient: GridClient, authFunction: WSRequest => WSRequest)(implicit ec: ExecutionContext, logMarker: LogMarker, instance: Instance): Future[Image] = { logger.info(logMarker, s"starting to aggregate image") val mediaId = image.id // NB original metadata should already be added, cleaned, and copied to metadata. diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/auth/ApiAccessor.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/auth/ApiAccessor.scala index 14bd411fa7b..6b7e06007d5 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/lib/auth/ApiAccessor.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/auth/ApiAccessor.scala @@ -1,7 +1,7 @@ package com.gu.mediaservice.lib.auth import com.gu.mediaservice.lib.argo.ArgoHelpers -import com.gu.mediaservice.lib.config.Services +import com.gu.mediaservice.lib.config.{InstanceForRequest, Services} import play.api.mvc.{RequestHeader, Result} sealed trait Tier @@ -18,7 +18,7 @@ object Tier { } case class ApiAccessor(identity: String, tier: Tier) -object ApiAccessor extends ArgoHelpers { +object ApiAccessor extends ArgoHelpers with InstanceForRequest { def apply(content: String): ApiAccessor = { val rows = content.split("\n") val name = rows.headOption.getOrElse("") @@ -30,7 +30,7 @@ object ApiAccessor extends ArgoHelpers { case Internal => true case ReadOnly => request.method == "GET" case Syndication => { - val isMediaApiRequest = request.uri.startsWith(services.apiBaseUri) // TODO check this! + val isMediaApiRequest = request.uri.startsWith(services.apiBaseUri(instanceOf(request))) // TODO check this! request.method == "GET" && isMediaApiRequest && request.path.startsWith("/images") } } diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/config/Services.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/config/Services.scala index 8bf7b282a5a..9bbe3b75098 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/lib/config/Services.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/config/Services.scala @@ -1,76 +1,80 @@ package com.gu.mediaservice.lib.config +import com.gu.mediaservice.model.Instance + trait Services { - def kahunaBaseUri: String + def kahunaBaseUri(instance: Instance): String - def apiBaseUri: String + def apiBaseUri(instance: Instance): String - def loaderBaseUri: String + def loaderBaseUri(instance: Instance): String - def projectionBaseUri: String + def projectionBaseUri(instance: Instance): String - def cropperBaseUri: String + def cropperBaseUri(instance: Instance): String - def metadataBaseUri: String + def metadataBaseUri(instance: Instance): String - def imgopsBaseUri: String + def imgopsBaseUri(instance: Instance): String - def usageBaseUri: String + def usageBaseUri(instance: Instance): String - def collectionsBaseUri: String + def collectionsBaseUri(instance: Instance): String - def leasesBaseUri: String + def leasesBaseUri(instance: Instance): String - def authBaseUri: String + def authBaseUri(instance: Instance): String - def thrallBaseUri: String + def thrallBaseUri(instance: Instance): String def guardianWitnessBaseUri: String - def corsAllowedDomains: Set[String] + def corsAllowedDomains(instance: Instance): Set[String] def redirectUriParam: String def redirectUriPlaceholder: String - def loginUriTemplate: String - + def loginUriTemplate(instance: Instance): String } -protected class SingleHostServices(val rootUrl: String) extends Services { - val kahunaBaseUri: String = rootUrl +protected class SingleHostServices(val domain: String) extends Services { + override def kahunaBaseUri(instance: Instance): String = vhostServiceName("", instance) - val apiBaseUri: String = subpathedServiceBaseUri("media-api") + override def apiBaseUri(instance: Instance): String = vhostServiceName("media-api", instance) - val loaderBaseUri: String = subpathedServiceBaseUri("image-loader") + override def loaderBaseUri(instance: Instance): String = vhostServiceName("image-loader", instance) - val projectionBaseUri: String = loaderBaseUri + override def projectionBaseUri(instance: Instance): String = vhostServiceName("projection", instance) - val cropperBaseUri: String = subpathedServiceBaseUri("cropper") + override def cropperBaseUri(instance: Instance): String = vhostServiceName("cropper", instance) - val metadataBaseUri: String = subpathedServiceBaseUri("metadata-editor") + override def metadataBaseUri(instance: Instance): String = vhostServiceName("metadata-editor", instance) - val imgopsBaseUri: String = subpathedServiceBaseUri("imgproxy") + override def imgopsBaseUri(instance: Instance): String= vhostServiceName("imgproxy", instance) - val usageBaseUri: String =subpathedServiceBaseUri("usage") + override def usageBaseUri(instance: Instance): String = vhostServiceName("usage", instance) - val collectionsBaseUri: String = subpathedServiceBaseUri("collections") + override def collectionsBaseUri(instance: Instance): String = vhostServiceName("collections", instance) - val leasesBaseUri: String = subpathedServiceBaseUri("leases") + override def leasesBaseUri(instance: Instance): String = vhostServiceName("leases", instance) - val authBaseUri: String = subpathedServiceBaseUri("auth") + override def authBaseUri(instance: Instance): String = vhostServiceName("auth", instance) - val thrallBaseUri: String = subpathedServiceBaseUri("thrall") + override def thrallBaseUri(instance: Instance): String = vhostServiceName("thrall", instance) val guardianWitnessBaseUri: String = "https://n0ticeapis.com" - val corsAllowedDomains: Set[String] = Set(kahunaBaseUri, apiBaseUri, thrallBaseUri) + override def corsAllowedDomains(instance: Instance): Set[String] = Set(kahunaBaseUri(instance), apiBaseUri(instance), thrallBaseUri(instance)) val redirectUriParam = "redirectUri" val redirectUriPlaceholder = s"{?$redirectUriParam}" - val loginUriTemplate = s"$authBaseUri/login$redirectUriPlaceholder" + def loginUriTemplate(instance: Instance): String = s"${authBaseUri(instance)}/login$redirectUriPlaceholder" - private def subpathedServiceBaseUri(serviceName: String): String = s"$rootUrl/$serviceName" + private def vhostServiceName(serviceName: String, instance: Instance): String = { + val vhost = instance.id + s"https://$vhost.$domain/" + serviceName + } } diff --git a/common-lib/src/main/scala/com/gu/mediaservice/model/Edits.scala b/common-lib/src/main/scala/com/gu/mediaservice/model/Edits.scala index eff2b51188a..3c4ef3ad8d5 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/model/Edits.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/model/Edits.scala @@ -2,11 +2,13 @@ package com.gu.mediaservice.model import java.net.{URI, URLEncoder} import com.gu.mediaservice.lib.argo.model.{Action, EmbeddedEntity} +import com.gu.mediaservice.lib.config.InstanceForRequest import org.joda.time.DateTime import play.api.libs.json._ import play.api.libs.json.JodaReads._ import play.api.libs.json.JodaWrites._ import play.api.libs.functional.syntax._ +import play.api.mvc.Request case class Edits( @@ -54,8 +56,8 @@ object Edits { } -trait EditsResponse { - val metadataBaseUri: String +trait EditsResponse extends InstanceForRequest { + val metadataBaseUri: Instance => String type ArchivedEntity = EmbeddedEntity[Boolean] type SetEntity = EmbeddedEntity[Seq[EmbeddedEntity[String]]] @@ -63,11 +65,12 @@ trait EditsResponse { type UsageRightsEntity = EmbeddedEntity[UsageRights] type PhotoshootEntity = EmbeddedEntity[Photoshoot] - def editsEmbeddedEntity(id: String, edits: Edits) = + def editsEmbeddedEntity(id: String, edits: Edits)(implicit instance: Instance) = { EmbeddedEntity(entityUri(id), Some(Json.toJson(edits)(editsEntity(id)))) + } // the types are in the arguments because of a whining scala compiler - def editsEntity(id: String): Writes[Edits] = ( + def editsEntity(id: String)(implicit instance: Instance): Writes[Edits] = ( (__ \ Edits.Archived).write[ArchivedEntity].contramap(archivedEntity(id, _: Boolean)) ~ (__ \ Edits.Labels).write[SetEntity].contramap(setEntity(id, "labels", _: List[String])) ~ (__ \ Edits.Metadata).write[MetadataEntity].contramap(metadataEntity(id, _: ImageMetadata)) ~ @@ -76,31 +79,31 @@ trait EditsResponse { (__ \ Edits.LastModified).writeNullable[DateTime] )(unlift(Edits.unapply)) - def photoshootEntity(id: String, photoshoot: Option[Photoshoot]): PhotoshootEntity = + def photoshootEntity(id: String, photoshoot: Option[Photoshoot])(implicit instance: Instance): PhotoshootEntity = EmbeddedEntity(entityUri(id, "/photoshoot"), photoshoot) - def archivedEntity(id: String, a: Boolean): ArchivedEntity = + def archivedEntity(id: String, a: Boolean)(implicit instance: Instance): ArchivedEntity = EmbeddedEntity(entityUri(id, "/archived"), Some(a)) - def metadataEntity(id: String, m: ImageMetadata): MetadataEntity = + def metadataEntity(id: String, m: ImageMetadata)(implicit instance: Instance): MetadataEntity = EmbeddedEntity(entityUri(id, "/metadata"), Some(m), actions = List( Action("set-from-usage-rights", entityUri(id, "/metadata/set-from-usage-rights"), "POST") )) - def usageRightsEntity(id: String, u: Option[UsageRights]): UsageRightsEntity = + def usageRightsEntity(id: String, u: Option[UsageRights])(implicit instance: Instance): UsageRightsEntity = u.map(i => EmbeddedEntity(entityUri(id, "/usage-rights"), Some(i))) .getOrElse(EmbeddedEntity(entityUri(id, "/usage-rights"), None)) - def setEntity(id: String, setName: String, labels: List[String]): SetEntity = + def setEntity(id: String, setName: String, labels: List[String])(implicit instance: Instance): SetEntity = EmbeddedEntity(entityUri(id, s"/$setName"), Some(labels.map(setUnitEntity(id, setName, _)))) - def setUnitEntity(id: String, setName: String, name: String): EmbeddedEntity[String] = + def setUnitEntity(id: String, setName: String, name: String)(implicit instance: Instance): EmbeddedEntity[String] = EmbeddedEntity(entityUri(id, s"/$setName/${URLEncoder.encode(name, "UTF-8")}"), Some(name)) - private def entityUri(id: String, endpoint: String = ""): URI = - URI.create(s"$metadataBaseUri/metadata/$id$endpoint") + private def entityUri(id: String, endpoint: String = "")(implicit instance: Instance): URI = + URI.create(s"${metadataBaseUri(instance)}/metadata/$id$endpoint") - def labelsUri(id: String) = entityUri(id, "/labels") + def labelsUri(id: String)(implicit instance: Instance) = entityUri(id, "/labels") - def metadataUri(id: String) = entityUri(id, "/metadata") + def metadataUri(id: String)(implicit instance: Instance) = entityUri(id, "/metadata") } diff --git a/cropper/app/controllers/CropperController.scala b/cropper/app/controllers/CropperController.scala index 0d6a1211dfa..43d48e47cbf 100644 --- a/cropper/app/controllers/CropperController.scala +++ b/cropper/app/controllers/CropperController.scala @@ -9,6 +9,7 @@ import com.gu.mediaservice.lib.auth.Authentication.Principal import com.gu.mediaservice.lib.auth.Permissions.{DeleteCropsOrUsages, PrincipalFilter} import com.gu.mediaservice.lib.auth._ import com.gu.mediaservice.lib.aws.UpdateMessage +import com.gu.mediaservice.lib.config.InstanceForRequest import com.gu.mediaservice.lib.imaging.ExportResult import com.gu.mediaservice.lib.logging.{LogMarker, MarkerMap} import com.gu.mediaservice.lib.play.RequestLoggingFilter @@ -32,22 +33,25 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no override val controllerComponents: ControllerComponents, authorisation: Authorisation, gridClient: GridClient)(implicit val ec: ExecutionContext) - extends BaseController with MessageSubjects with ArgoHelpers with MediaApiUrls { + extends BaseController with MessageSubjects with ArgoHelpers with MediaApiUrls with InstanceForRequest { // Stupid name clash between Argo and Play import com.gu.mediaservice.lib.argo.model.{Action => ArgoAction} val AuthenticatedAndAuthorisedToDeleteCrops = auth andThen authorisation.CommonActionFilters.authorisedForDeleteCropsOrUsages - val indexResponse = { + private def indexResponse()(implicit instance: Instance) = { val indexData = Map("description" -> "This is the Cropper Service") val indexLinks = List( - Link("crop", s"${config.rootUri}/crops") + Link("crop", s"${config.rootUri(instance)}/crops") ) respond(indexData, indexLinks) } - def index = auth { indexResponse } + def index = auth { request => + implicit val instance: Instance = instanceOf(request) + indexResponse() + } def addExport = auth.async(parse.json) { httpRequest => httpRequest.body.validate[ExportRequest] map { exportRequest => @@ -59,7 +63,7 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no val user = httpRequest.user val onBehalfOfPrincipal = auth.getOnBehalfOfPrincipal(user) - executeRequest(exportRequest, user, onBehalfOfPrincipal).map { case (imageId, export) => + executeRequest(exportRequest, user, onBehalfOfPrincipal, httpRequest).map { case (imageId, export) => val cropJson = Json.toJson(export).as[JsObject] val updateMessage = UpdateMessage(subject = UpdateImageExports, id = Some(imageId), crops = Some(Seq(export))) @@ -106,7 +110,7 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no private def downloadExportLink(imageId: String, exportId: String, width: Int) = Link(s"crop-download-$exportId-$width", s"${config.apiUri}/images/$imageId/export/$exportId/asset/$width/download") def getCrops(id: String) = auth.async { httpRequest => - + val instance = instanceOf(httpRequest) implicit val logMarker: LogMarker = MarkerMap( "requestType" -> "getCrops", "requestId" -> RequestLoggingFilter.getRequestId(httpRequest), @@ -117,7 +121,7 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no store.listCrops(id) map (_.toList) map { crops => val deleteCropsAction = - ArgoAction("delete-crops", URI.create(s"${config.rootUri}/crops/$id"), "DELETE") + ArgoAction("delete-crops", URI.create(s"${config.rootUri(instance)}/crops/$id"), "DELETE") lazy val cropDownloadLinks = for { crop <- crops @@ -160,11 +164,12 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no } def executeRequest( - exportRequest: ExportRequest, user: Principal, onBehalfOfPrincipal: Authentication.OnBehalfOfPrincipal + exportRequest: ExportRequest, user: Principal, onBehalfOfPrincipal: Authentication.OnBehalfOfPrincipal, + request: Authentication.Request[JsValue] )(implicit logMarker: LogMarker): Future[(String, Crop)] = { - + implicit val instance: Instance = instanceOf(request) for { - _ <- verify(isMediaApiImageUri(exportRequest.uri, config.apiUri), InvalidSource) + _ <- verify(isMediaApiImageUri(exportRequest.uri, config.apiUri(instanceOf(request))), InvalidSource) apiImage <- fetchSourceFromApi(exportRequest.uri, onBehalfOfPrincipal) _ <- verify(apiImage.valid, InvalidImage) // Image should always have dimensions, but we want to safely extract the Option @@ -184,7 +189,7 @@ class CropperController(auth: Authentication, crops: Crops, store: CropStore, no } yield (id, finalCrop) } - private def fetchSourceFromApi(uri: String, onBehalfOfPrincipal: Authentication.OnBehalfOfPrincipal): Future[SourceImage] = { + private def fetchSourceFromApi(uri: String, onBehalfOfPrincipal: Authentication.OnBehalfOfPrincipal)(implicit instance: Instance): Future[SourceImage] = { gridClient.getSourceImage(imageIdFrom(uri), onBehalfOfPrincipal) } diff --git a/cropper/app/lib/CropperConfig.scala b/cropper/app/lib/CropperConfig.scala index 25f78a5dacc..ef762ed2a14 100644 --- a/cropper/app/lib/CropperConfig.scala +++ b/cropper/app/lib/CropperConfig.scala @@ -1,6 +1,7 @@ package lib import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources} +import com.gu.mediaservice.model.Instance import java.io.File @@ -14,8 +15,8 @@ class CropperConfig(resources: GridConfigResources) extends CommonConfig(resourc // Note: work around CloudFormation not allowing optional parameters val imgPublishingSecureHost = stringOpt("publishing.image.secure.host").filterNot(_.isEmpty) - val rootUri = services.cropperBaseUri - val apiUri = services.apiBaseUri + val rootUri: Instance => String = services.cropperBaseUri + val apiUri: Instance => String = services.apiBaseUri val tempDir: File = new File(stringDefault("crop.output.tmp.dir", "/tmp")) diff --git a/image-loader/app/controllers/ImageLoaderController.scala b/image-loader/app/controllers/ImageLoaderController.scala index cde5129fe82..9305774c216 100644 --- a/image-loader/app/controllers/ImageLoaderController.scala +++ b/image-loader/app/controllers/ImageLoaderController.scala @@ -12,19 +12,22 @@ import com.gu.mediaservice.lib.argo.model.Link import com.gu.mediaservice.lib.auth.Authentication.OnBehalfOfPrincipal import com.gu.mediaservice.lib.auth._ import com.gu.mediaservice.lib.aws.{S3Ops, SimpleSqsMessageConsumer, SqsHelpers} +import com.gu.mediaservice.lib.config.InstanceForRequest import com.gu.mediaservice.lib.formatting.printDateTime import com.gu.mediaservice.lib.logging.{FALLBACK, LogMarker, MarkerMap} import com.gu.mediaservice.lib.play.RequestLoggingFilter import com.gu.mediaservice.lib.{DateTimeUtils, ImageIngestOperations, ImageStorageProps} -import com.gu.mediaservice.model.{UnsupportedMimeTypeException, UploadInfo} -import org.scanamo.{ConditionNotMet, ScanamoError} +import com.gu.mediaservice.model.{Instance, UnsupportedMimeTypeException, UploadInfo} import lib.FailureResponse.Response -import lib.imaging.{MimeTypeDetection, NoSuchImageExistsInS3, UserImageLoaderException} -import lib.storage.{ImageLoaderStore, S3FileDoesNotExistException} import lib._ +import lib.imaging.{MimeTypeDetection, NoSuchImageExistsInS3, UserImageLoaderException} +import lib.storage.ImageLoaderStore import model.upload.UploadRequest import model.{Projector, QuarantineUploader, S3FileExtractedMetadata, S3IngestObject, StatusType, UploadStatus, UploadStatusRecord, UploadStatusUri, Uploader} +import org.apache.pekko.Done +import org.apache.pekko.stream.Materializer import org.joda.time.{DateTime, DateTimeZone} +import org.scanamo.{ConditionNotMet, ScanamoError} import play.api.data.Form import play.api.data.Forms._ import play.api.inject.ApplicationLifecycle @@ -34,6 +37,7 @@ import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.cloudwatch.model.Dimension import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model.{GetObjectRequest, HeadObjectRequest, NoSuchKeyException} +import software.amazon.awssdk.services.sqs.model.{Message => SQSMessage} import java.io.{File, FileOutputStream} import java.net.URI @@ -60,7 +64,7 @@ class ImageLoaderController(auth: Authentication, metrics: ImageLoaderMetrics, applicationLifecycle: ApplicationLifecycle) (implicit val ec: ExecutionContext, materializer: Materializer) - extends BaseController with ArgoHelpers with SqsHelpers { + extends BaseController with ArgoHelpers with SqsHelpers with InstanceForRequest { private val AuthenticatedAndAuthorised = auth andThen authorisation.CommonActionFilters.authorisedForUpload @@ -76,6 +80,9 @@ class ImageLoaderController(auth: Authentication, } }) + //TODO first example of a non user facing message which needs to be made instance aware! + val maybeIngestQueueAndProcessor: Option[(SimpleSqsMessageConsumer, Future[Done])] = None + /* val maybeIngestQueueAndProcessor: Option[(SimpleSqsMessageConsumer, Future[Done])] = maybeIngestQueue.map { ingestQueue => val processor = Source.repeat(()) .mapAsyncUnordered(parallelism=1)(_ => { @@ -115,26 +122,29 @@ class ImageLoaderController(auth: Authentication, (ingestQueue, processor) } + */ - private lazy val indexResponse: Result = { + private def indexResponse(instance: Instance): Result = { val indexData = Map("description" -> "This is the Loader Service") val indexLinks = List( - Link("prepare", s"${config.rootUri}/prepare"), - Link("uploadStatus", s"${config.rootUri}/uploadStatus/{id}"), - Link("uploadStatuses", s"${config.rootUri}/uploadStatuses"), - Link("load", s"${config.rootUri}/images{?uploadedBy,identifiers,uploadTime,filename}"), - Link("import", s"${config.rootUri}/imports{?uri,uploadedBy,identifiers,uploadTime,filename}") + Link("prepare", s"${config.rootUri(instance)}/prepare"), + Link("uploadStatus", s"${config.rootUri(instance)}/uploadStatus/{id}"), + Link("uploadStatuses", s"${config.rootUri(instance)}/uploadStatuses"), + Link("load", s"${config.rootUri(instance)}/images{?uploadedBy,identifiers,uploadTime,filename}"), + Link("import", s"${config.rootUri(instance)}/imports{?uri,uploadedBy,identifiers,uploadTime,filename}") ) respond(indexData, indexLinks) } - def index: Action[AnyContent] = AuthenticatedAndAuthorised { indexResponse } + def index: Action[AnyContent] = AuthenticatedAndAuthorised { request => + indexResponse(instanceOf(request)) + } - private def quarantineOrStoreImage(uploadRequest: UploadRequest)(implicit logMarker: LogMarker) = { - quarantineUploader.map(_.quarantineFile(uploadRequest)).getOrElse(for { uploadStatusUri <- uploader.storeFile(uploadRequest)} yield{uploadStatusUri.toJsObject}) + private def quarantineOrStoreImage(uploadRequest: UploadRequest)(implicit logMarker: LogMarker, instance: Instance) = { + quarantineUploader.map(_.quarantineFile(uploadRequest)(instance)).getOrElse(for { uploadStatusUri <- uploader.storeFile(uploadRequest)} yield{uploadStatusUri.toJsObject}) } - private def handleMessageFromIngestBucket(sqsMessage:SQSMessage)(basicLogMarker: LogMarker): Future[Unit] = Future[Future[Unit]]{ + private def handleMessageFromIngestBucket(sqsMessage:SQSMessage)(basicLogMarker: LogMarker, request: Request[AnyContent]): Future[Unit] = Future[Future[Unit]]{ logger.info(basicLogMarker, sqsMessage.toString) @@ -186,7 +196,7 @@ class ImageLoaderController(auth: Authentication, } Future.unit } else { - attemptToProcessIngestedFile(s3IngestObject, isUiUpload)(logMarker) map { digestedFile => + attemptToProcessIngestedFile(s3IngestObject, isUiUpload)(logMarker, request) map { digestedFile => metrics.successfulIngestsFromQueue.incrementBothWithAndWithoutDimensions(metricDimensions) logger.info(logMarker, s"Successfully processed image ${digestedFile.file.getName}") store.deleteObjectFromIngestBucket(s3IngestObject.key) @@ -204,7 +214,7 @@ class ImageLoaderController(auth: Authentication, } }.flatten - private def attemptToProcessIngestedFile(s3IngestObject:S3IngestObject, isUiUpload: Boolean)(initialLogMarker:LogMarker): Future[DigestedFile] = { + private def attemptToProcessIngestedFile(s3IngestObject:S3IngestObject, isUiUpload: Boolean)(initialLogMarker:LogMarker, request: Request[AnyContent]): Future[DigestedFile] = { logger.info(initialLogMarker, "Attempting to process file") val tempFile = createTempFile("s3IngestBucketFile")(initialLogMarker) @@ -218,6 +228,7 @@ class ImageLoaderController(auth: Authentication, "mediaId" -> digestedFile.digest ) + implicit val instance: Instance = instanceOf(request) val futureUploadStatusUri = uploadDigestedFileToStore( digestedFileFuture = Future(digestedFile), uploadedBy = s3IngestObject.uploadedBy, @@ -271,7 +282,7 @@ class ImageLoaderController(auth: Authentication, .map(Ok(_)) } - def loadImage(uploadedBy: Option[String], identifiers: Option[String], uploadTime: Option[String], filename: Option[String]): Action[DigestedFile] = { + def loadImage(uploadedBy: Option[String], identifiers: Option[String], uploadTime: Option[String], filename: Option[String]): Action[DigestedFile] = { val uploadTimeToRecord = DateTimeUtils.fromValueOrNow(uploadTime) val initialContext = MarkerMap( @@ -309,7 +320,9 @@ class ImageLoaderController(auth: Authentication, filename.flatMap(_.trim.nonEmptyOpt) ) _ <- uploadStatusTable.setStatus(record) - result <- quarantineOrStoreImage(uploadRequest) + + result <- quarantineOrStoreImage(uploadRequest)(context, instanceOf(req)) + } yield result result.onComplete( _ => Try { deleteTempFile(tempFile) } ) @@ -378,6 +391,7 @@ class ImageLoaderController(auth: Authentication, ) val tempFile = createTempFile(s"projection-$imageId")(initialContext) auth.async { req => + implicit val instance: Instance = instanceOf(req) implicit val context: LogMarker = initialContext ++ Map( "requestId" -> RequestLoggingFilter.getRequestId(req) ) @@ -432,7 +446,7 @@ class ImageLoaderController(auth: Authentication, identifiers.map(Json.parse(_).as[Map[String, String]]).getOrElse(Map.empty), uploadTime, filename - ) + )(context, instanceOf(request)) // under all circumstances, remove the temp files uploadResultFuture.onComplete { _ => @@ -459,7 +473,7 @@ class ImageLoaderController(auth: Authentication, identifiers: Map[String, String], uploadTime: Option[String], filename: Option[String] - )(implicit logMarker:LogMarker): Future[UploadStatusUri] = { + )(implicit logMarker:LogMarker, instance: Instance): Future[UploadStatusUri] = { for { digestedFile <- digestedFileFuture @@ -574,7 +588,7 @@ class ImageLoaderController(auth: Authentication, private case class RestoreFromReplicaForm(imageId: String) def restoreFromReplica: Action[AnyContent] = AuthenticatedAndAuthorised.async { implicit request => - + implicit val instance: Instance = instanceOf(request) val imageId = Form( mapping( "imageId" -> text @@ -636,7 +650,7 @@ class ImageLoaderController(auth: Authentication, future.map { _ => logger.info(logMarker, s"Restored image $imageId from replica bucket $replicaBucket (key: $s3Key)") - Redirect(s"${config.kahunaUri}/images/$imageId") + Redirect(s"${config.kahunaUri(instance)}/images/$imageId") } case _ => Future.successful(NotFound("Image not found in replica bucket")) diff --git a/image-loader/app/controllers/UploadStatusController.scala b/image-loader/app/controllers/UploadStatusController.scala index 4bde0c8b3be..172acccf248 100644 --- a/image-loader/app/controllers/UploadStatusController.scala +++ b/image-loader/app/controllers/UploadStatusController.scala @@ -3,6 +3,7 @@ package controllers import com.gu.mediaservice.lib.argo.ArgoHelpers import com.gu.mediaservice.lib.auth._ +import com.gu.mediaservice.lib.config.InstanceForRequest import lib._ import model.{StatusType, UploadStatus} import org.scanamo.{ConditionNotMet, ScanamoError} @@ -18,13 +19,13 @@ class UploadStatusController(auth: Authentication, authorisation: Authorisation ) (implicit val ec: ExecutionContext) - extends BaseController with ArgoHelpers { + extends BaseController with ArgoHelpers with InstanceForRequest { - def getUploadStatus(imageId: String) = auth.async { + def getUploadStatus(imageId: String) = auth.async { request => store.getStatus(imageId) .map { case Some(Right(record)) => respond(UploadStatus(record.status, record.errorMessage), - uri = Some(URI.create(s"${config.apiUri}/images/${imageId}"))) + uri = Some(URI.create(s"${config.apiUri(instanceOf(request))}/images/$imageId"))) case Some(Left(error)) => respondError(BadRequest, "cannot-get", s"Cannot get upload status ${error}") case None => respondNotFound(s"No upload status found for image id: ${imageId}") } diff --git a/image-loader/app/lib/ImageLoaderConfig.scala b/image-loader/app/lib/ImageLoaderConfig.scala index 9417585a471..be51b2ff4a5 100644 --- a/image-loader/app/lib/ImageLoaderConfig.scala +++ b/image-loader/app/lib/ImageLoaderConfig.scala @@ -24,9 +24,9 @@ class ImageLoaderConfig(resources: GridConfigResources) extends CommonConfig(res val thumbWidth: Int = 256 val thumbQuality: Double = 85d // out of 100 - val rootUri: String = services.loaderBaseUri - val apiUri: String = services.apiBaseUri - val kahunaUri: String = services.kahunaBaseUri + val rootUri: Instance => String = services.loaderBaseUri + val apiUri: Instance => String = services.apiBaseUri + val kahunaUri: Instance => String = services.kahunaBaseUri val transcodedMimeTypes: List[MimeType] = getStringSet("transcoded.mime.types").toList.map(MimeType(_)) val supportedMimeTypes: List[MimeType] = List(Jpeg, Png) ::: transcodedMimeTypes diff --git a/image-loader/app/model/Projector.scala b/image-loader/app/model/Projector.scala index 41dcf25fa00..874b6078af4 100644 --- a/image-loader/app/model/Projector.scala +++ b/image-loader/app/model/Projector.scala @@ -13,7 +13,7 @@ import com.gu.mediaservice.lib.cleanup.ImageProcessor import com.gu.mediaservice.lib.imaging.ImageOperations import com.gu.mediaservice.lib.logging.{GridLogging, LogMarker, Stopwatch} import com.gu.mediaservice.lib.net.URI -import com.gu.mediaservice.model.{Image, MimeType, UploadInfo} +import com.gu.mediaservice.model.{Image, Instance, MimeType, UploadInfo} import lib.imaging.{MimeTypeDetection, NoSuchImageExistsInS3} import lib.{DigestedFile, ImageLoaderConfig} import model.upload.UploadRequest @@ -85,7 +85,7 @@ class Projector(config: ImageUploadOpsCfg, private val imageUploadProjectionOps = new ImageUploadProjectionOps(config, imageOps, processor, s3, maybeEmbedder) def projectS3ImageById(imageId: String, tempFile: File, gridClient: GridClient, onBehalfOfFn: WSRequest => WSRequest) - (implicit ec: ExecutionContext, logMarker: LogMarker): Future[Option[Image]] = { + (implicit ec: ExecutionContext, logMarker: LogMarker, instance: Instance): Future[Option[Image]] = { Future { import ImageIngestOperations.fileKeyFromId val s3Key = fileKeyFromId(imageId) @@ -125,7 +125,7 @@ class Projector(config: ImageUploadOpsCfg, extractedS3Meta: S3FileExtractedMetadata, gridClient: GridClient, onBehalfOfFn: WSRequest => WSRequest) - (implicit ec: ExecutionContext, logMarker: LogMarker): Future[Image] = { + (implicit ec: ExecutionContext, logMarker: LogMarker, instance: Instance): Future[Image] = { val DigestedFile(tempFile_, id_) = srcFileDigest val identifiers_ = extractedS3Meta.identifiers diff --git a/image-loader/app/model/QuarantineUploader.scala b/image-loader/app/model/QuarantineUploader.scala index af75e44a73c..766c5ac6e44 100644 --- a/image-loader/app/model/QuarantineUploader.scala +++ b/image-loader/app/model/QuarantineUploader.scala @@ -18,6 +18,8 @@ import play.api.Logger import play.api.libs.json.{JsObject, Json} import com.gu.mediaservice.lib.formatting._ import model.upload.UploadRequest +import play.api.mvc.{AnyContent, Request} + import java.net.URLEncoder import java.nio.charset.StandardCharsets import scala.concurrent.{ExecutionContext, Future} @@ -37,7 +39,7 @@ class QuarantineUploader(val store: QuarantineStore, ) } - def quarantineFile(uploadRequest: UploadRequest)( + def quarantineFile(uploadRequest: UploadRequest)(instance: Instance) ( implicit ec: ExecutionContext, logMarker: LogMarker): Future[JsObject] = { @@ -45,7 +47,7 @@ class QuarantineUploader(val store: QuarantineStore, for { _ <- storeQuarantineFile(uploadRequest) - uri = s"${config.rootUri}/uploadStatus/${uploadRequest.imageId}" + uri = s"${config.rootUri(instance)}/uploadStatus/${uploadRequest.imageId}" } yield { Json.obj("uri" -> uri) } diff --git a/image-loader/app/model/Uploader.scala b/image-loader/app/model/Uploader.scala index 6e6c7acc26b..f28d329b117 100644 --- a/image-loader/app/model/Uploader.scala +++ b/image-loader/app/model/Uploader.scala @@ -1,14 +1,12 @@ package model -import com.gu.mediaservice.{GridClient, ImageDataMerger} +import _root_.play.api.libs.json.Json +import _root_.play.api.libs.ws.WSRequest import com.gu.mediaservice.lib.Files.createTempFile import com.gu.mediaservice.lib.ImageIngestOperations.fileKeyFromId - -import java.io.File -import java.nio.file.{Files, Path} +import com.gu.mediaservice.lib._ import com.gu.mediaservice.lib.argo.ArgoHelpers import com.gu.mediaservice.lib.auth.Authentication -import com.gu.mediaservice.lib.{BrowserViewableImage, ImageStorageProps, StorableOptimisedImage, StorableOriginalImage, StorableThumbImage} import com.gu.mediaservice.lib.aws.{Embedder, EmbedderMessage, S3Object, UpdateMessage} import com.gu.mediaservice.lib.cleanup.ImageProcessor import com.gu.mediaservice.lib.formatting._ @@ -19,19 +17,18 @@ import com.gu.mediaservice.lib.metadata.{FileMetadataHelper, ImageMetadataConver import com.gu.mediaservice.lib.net.URI import com.gu.mediaservice.model._ import com.gu.mediaservice.syntax.MessageSubjects -import lib.{DigestedFile, ImageLoaderConfig, Notifications} +import com.gu.mediaservice.{GridClient, ImageDataMerger} import lib.imaging.{FileMetadataReader, MimeTypeDetection} import lib.storage.ImageLoaderStore +import lib.{DigestedFile, ImageLoaderConfig, Notifications} import model.Uploader.{fromUploadRequestShared, toImageUploadOpsCfg} import model.upload.{OptimiseOps, OptimiseWithPngQuant, UploadRequest} import org.joda.time.DateTime -import play.api.libs.json.Json -import play.api.libs.ws.WSRequest import software.amazon.awssdk.services.s3.model.CopyObjectRequest -import scala.collection.compat._ +import java.io.File +import java.nio.file.Files import scala.concurrent.{ExecutionContext, Future} -import scala.jdk.CollectionConverters.MapHasAsScala case class ImageUpload(uploadRequest: UploadRequest, image: Image) @@ -350,7 +347,7 @@ class Uploader( isReplacement: Boolean )( mediaIdToAddUsageTo: String - ) = { + )(implicit instance: Instance) = { gridClient.postUsage( usageType = "child", data = Json.obj( @@ -367,7 +364,7 @@ class Uploader( } private def fromUploadRequest(uploadRequest: UploadRequest) - (implicit logMarker: LogMarker): Future[ImageUpload] = { + (implicit logMarker: LogMarker, instance: Instance): Future[ImageUpload] = { val sideEffectDependencies = ImageUploadOpsDependencies(toImageUploadOpsCfg(config), imageOps, storeSource, storeThumbnail, storeOptimisedImage) Stopwatch.async("finalImage") { @@ -436,7 +433,7 @@ class Uploader( def storeFile(uploadRequest: UploadRequest) (implicit ec:ExecutionContext, - logMarker: LogMarker): Future[UploadStatusUri] = { + logMarker: LogMarker, instance: Instance): Future[UploadStatusUri] = { logger.info(logMarker, "Storing file") @@ -487,7 +484,7 @@ class Uploader( } } - UploadStatusUri(s"${config.rootUri}/uploadStatus/${uploadRequest.imageId}") + UploadStatusUri(s"${config.rootUri(instance)}/uploadStatus/${uploadRequest.imageId}") } } @@ -496,7 +493,8 @@ class Uploader( gridClient: GridClient, onBehalfOfFn: WSRequest => WSRequest) (implicit ec: ExecutionContext, - logMarker: LogMarker): Future[Unit] = for { + logMarker: LogMarker, + instance: Instance): Future[Unit] = for { imageUpload <- fromUploadRequest(uploadRequest) imageWithoutUserEdits = imageUpload.image imageWithUserEditsApplied <- ImageDataMerger.aggregate(imageWithoutUserEdits, gridClient, onBehalfOfFn) diff --git a/image-loader/test/scala/model/ProjectorTest.scala b/image-loader/test/scala/model/ProjectorTest.scala index 4de3767ef3f..44dffff8d17 100644 --- a/image-loader/test/scala/model/ProjectorTest.scala +++ b/image-loader/test/scala/model/ProjectorTest.scala @@ -48,6 +48,8 @@ class ProjectorTest extends AnyFreeSpec with Matchers with ScalaFutures with Moc private val auth = mock[Authentication] private val projector = new Projector(config, s3, imageOperations, ImageProcessor.identity, auth, maybeEmbedder) + private implicit val instance: Instance = Instance("an-instance") + // FIXME temporary ignored as test is not executable in CI/CD machine // because graphic lib files like srgb.icc, cmyk.icc are in root directory instead of resources // this test is passing when running on local machine diff --git a/kahuna/app/KahunaComponents.scala b/kahuna/app/KahunaComponents.scala index 9c179e485c5..69bbe06f320 100644 --- a/kahuna/app/KahunaComponents.scala +++ b/kahuna/app/KahunaComponents.scala @@ -23,26 +23,27 @@ object KahunaSecurityConfig { val base = SecurityHeadersConfig.fromConfiguration(playConfig) val services = List( - config.services.apiBaseUri, - config.services.loaderBaseUri, - config.services.cropperBaseUri, - config.services.metadataBaseUri, - config.services.imgopsBaseUri, - config.services.usageBaseUri, - config.services.collectionsBaseUri, - config.services.leasesBaseUri, - config.services.authBaseUri, + // TODO Restore config.services.apiBaseUri, + // TODO Restore config.services.loaderBaseUri, + // TODO Restore config.services.cropperBaseUri, + // TODO Restore config.services.metadataBaseUri, + // TODO restore config.services.imgopsBaseUri, + // TODO Restore config.services.usageBaseUri, + // TODO Restore config.services.collectionsBaseUri, + // TODO Restore config.services.leasesBaseUri, + // TODO Restore config.services.authBaseUri, config.services.guardianWitnessBaseUri ) - val frameSources = s"frame-src ${config.services.authBaseUri} ${config.services.kahunaBaseUri} https://accounts.google.com https://www.youtube.com ${config.scriptsToLoad.map(_.host).mkString(" ")}" + // TODO restore ${config.services.authBaseUri} ${config.services.kahunaBaseUri} + val frameSources = s"frame-src https://accounts.google.com https://www.youtube.com ${config.scriptsToLoad.map(_.host).mkString(" ")}" val frameAncestors = s"frame-ancestors ${config.frameAncestors.mkString(" ")}" val connectSources = s"connect-src 'self' ${(services :+ config.imageOrigin).mkString(" ")} ${config.connectSources.mkString(" ")}" val imageSources = s"img-src ${List( "data:", "blob:", - URI.ensureSecure(config.services.imgopsBaseUri).toString, + // TODO restore URI.ensureSecure(config.services.imgopsBaseUri).toString, URI.ensureSecure(config.fullOrigin).toString, URI.ensureSecure(config.thumbOrigin).toString, URI.ensureSecure(config.cropOrigin).toString, diff --git a/kahuna/app/controllers/KahunaController.scala b/kahuna/app/controllers/KahunaController.scala index 816350e42e6..eee89984489 100644 --- a/kahuna/app/controllers/KahunaController.scala +++ b/kahuna/app/controllers/KahunaController.scala @@ -3,13 +3,14 @@ package controllers import com.gu.mediaservice.lib.argo.ArgoHelpers import com.gu.mediaservice.lib.auth.Authentication.Principal import com.gu.mediaservice.lib.auth.{Authentication, Authorisation, BaseControllerWithLoginRedirects} -import lib.{ExampleSwitch, FeatureSwitches, KahunaConfig, UseCqlChips} +import lib.{ExampleSwitch, FeatureSwitches, KahunaConfig, KahunaClientServiceUrls, UseCqlChips} import play.api.mvc.ControllerComponents import play.api.libs.json._ import scala.concurrent.ExecutionContext import com.gu.mediaservice.lib.config.FieldAlias._ -import com.gu.mediaservice.lib.config.Services +import com.gu.mediaservice.lib.config.{InstanceForRequest, Services} +import com.gu.mediaservice.model.Instance import play.api.mvc.Security.AuthenticatedRequest import play.twirl.api.Html @@ -20,13 +21,14 @@ class KahunaController( authorisation: Authorisation )( implicit val ec: ExecutionContext -) extends BaseControllerWithLoginRedirects with ArgoHelpers { +) extends BaseControllerWithLoginRedirects with ArgoHelpers with InstanceForRequest { override def auth: Authentication = authentication override def services: Services = config.services def index(ignored: String) = withOptionalLoginRedirect { request => + implicit val instance: Instance = instanceOf(request) val maybeUser: Option[Authentication.Principal] = request match { case authedRequest: AuthenticatedRequest[_, _] => authedRequest.user match { @@ -55,7 +57,7 @@ class KahunaController( val metadataTemplates: String = Json.toJson(config.metadataTemplates).toString() val announcements: String = Json.toJson(config.announcements).toString() val interimFilterOptions: String = Json.toJson(config.interimFilterOptions).toString() - val returnUri = config.rootUri + okPath + val returnUri = config.rootUri(instance) + okPath val costFilterLabel = config.costFilterLabel.getOrElse("Free to use only") val costFilterChargeable = config.costFilterChargeable.getOrElse(false) val maybeOrgOwnedValue = @@ -66,6 +68,13 @@ class KahunaController( val imageTypes = Json.toJson(config.imageTypes).toString() val agencyPicksIngredients = Json.toJson(config.agencyPicksIngredients).toString() + val rootUri = config.rootUri(instance) + + val kahunaClientServiceUrls = KahunaClientServiceUrls( + rootUri = rootUri, + mediaApiUri = config.mediaApiUri(instance) + ) + Ok(views.html.main( s"${config.authUri}/login?redirectUri=$returnUri", fieldAliases, @@ -81,12 +90,13 @@ class KahunaController( config, featureSwitchesJson, imageTypes, - agencyPicksIngredients + agencyPicksIngredients, + kahunaClientServiceUrls )) } def quotas = authentication { req => - Ok(views.html.quotas(config.mediaApiUri)) + Ok(views.html.quotas(config.mediaApiUri(instanceOf(req)))) } def notifications = authentication { req => diff --git a/kahuna/app/lib/KahunaClientServiceUrls.scala b/kahuna/app/lib/KahunaClientServiceUrls.scala new file mode 100644 index 00000000000..0ae1562c0fc --- /dev/null +++ b/kahuna/app/lib/KahunaClientServiceUrls.scala @@ -0,0 +1,3 @@ +package lib + +case class KahunaClientServiceUrls(rootUri: String, mediaApiUri: String) diff --git a/kahuna/app/lib/KahunaConfig.scala b/kahuna/app/lib/KahunaConfig.scala index 1daa8623d4e..73a20155634 100644 --- a/kahuna/app/lib/KahunaConfig.scala +++ b/kahuna/app/lib/KahunaConfig.scala @@ -3,6 +3,7 @@ package lib import com.gu.mediaservice.lib.auth.Permissions.Pinboard import com.gu.mediaservice.lib.auth.SimplePermission import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources} +import com.gu.mediaservice.model.Instance import play.api.libs.json._ case class ScriptToLoad( @@ -14,9 +15,9 @@ case class ScriptToLoad( ) class KahunaConfig(resources: GridConfigResources) extends CommonConfig(resources) { - val rootUri: String = services.kahunaBaseUri - val mediaApiUri: String = services.apiBaseUri - val authUri: String = services.authBaseUri + val rootUri: Instance => String = services.kahunaBaseUri + def mediaApiUri: Instance => String = services.apiBaseUri + val authUri: Instance => String = services.authBaseUri val sentryDsn: Option[String] = stringOpt("sentry.dsn").filterNot(_.isEmpty) diff --git a/kahuna/app/views/main.scala.html b/kahuna/app/views/main.scala.html index b09de2ed432..49520e41ae9 100644 --- a/kahuna/app/views/main.scala.html +++ b/kahuna/app/views/main.scala.html @@ -1,6 +1,7 @@ @import lib.ScriptToLoad @import lib.KahunaConfig +@import lib.KahunaClientServiceUrls @( reauthUri: String, fieldAliases: String, @@ -16,7 +17,8 @@ kahunaConfig: KahunaConfig, featureSwitches: String, imageTypes: String, - agencyPicksIngredients: String + agencyPicksIngredients: String, + kahunaClientServiceUrls: KahunaClientServiceUrls ) @@ -29,13 +31,13 @@ - + - + @@ -53,7 +55,7 @@