diff --git a/LewisOmniscientDebugger-1.5.jar b/LewisOmniscientDebugger-1.5.jar deleted file mode 100755 index 70dfb60..0000000 Binary files a/LewisOmniscientDebugger-1.5.jar and /dev/null differ diff --git a/README.md b/README.md index 80916fe..a19723b 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ compile group: 'com.neuronrobotics', name: 'LewisOmniscientDebugger', version: ' # Usage If you normally run your program on UNIX like this: -`` % java com.lambda.tests.TestMyArrayList `` +`` % java your.package.Main `` You can run the debugger like this: -`` % java -cp LewisOmniscientDebugger-1.5.jar:$CLASSPATH com.lambda.Debugger.Debugger com.lambda.tests.TestMyArrayList `` +`` % java -cp build/libs/LewisOmniscientDebugger.jar:$CLASSPATH com.lambda.Debugger.Debugger your.package.Main `` There are alias files and .BAT files that allow you to type this: diff --git a/build.gradle b/build.gradle index 627b77c..24df486 100644 --- a/build.gradle +++ b/build.gradle @@ -24,20 +24,21 @@ repositories { } dependencies { - implementation 'org.apache.bcel:bcel:6.2' + implementation 'org.apache.bcel:bcel:6.12.0' + implementation 'org.apache.commons:commons-lang3:3.20.0' + implementation 'commons-io:commons-io:2.21.0' implementation 'org.ow2.asm:asm:9.7.1' testImplementation 'junit:junit:4.13.2' } +apply from: 'gradle/runtime-provenance.gradle' + def manifestAttributes = [ 'Manifest-Version' : '1.0', - 'Created-By' : 'Neuron Robotics Cooperative', 'Specification-Title' : 'LewisOmniscientDebugger', 'Specification-Version' : java.targetCompatibility, - 'Specification-Vendor' : 'Neuron Robotics Cooperative', 'Implementation-Title' : 'LewisOmniscientDebugger', 'Implementation-Version' : java.targetCompatibility, - 'Implementation-Vendor' : 'Neuron Robotics Cooperative', 'Main-Class' : 'com.lambda.Debugger.Debugger', ] @@ -59,6 +60,9 @@ def launchProgramClasses = { File dir -> def debuggerDebuggerGeneratedSourceDir = layout.buildDirectory.dir('generated/sources/debuggerDebugger/java') def debuggerDebuggerMainClassesDir = layout.buildDirectory.dir('debuggerDebugger/mainClasses') def debuggerDebuggerWorkDir = layout.buildDirectory.dir('debuggerDebugger/work') +def pristineRuntimeClassesDir = layout.buildDirectory.dir('runtime/pristineClasses') +def instrumentedRuntimeClassesDir = layout.buildDirectory.dir('runtime/instrumentedClasses') +def runtimeInstrumentationWorkDir = layout.buildDirectory.dir('runtime/work') def debuggerDebuggerSourceReplacements = [ ['com.lambda', 'lambda'], ['edu.insa.LSD', 'insa.LSD'], @@ -82,6 +86,9 @@ sourceSets { } } +def compiledMainClassesDirs = files(sourceSets.main.output.classesDirs.files) +sourceSets.main.output.setClassesDirs(files(instrumentedRuntimeClassesDir)) + tasks.register('generateDebuggerDebuggerSources') { description = 'Generate the package-rewritten ODB sources used to debug ODB itself' group = 'build' @@ -130,17 +137,43 @@ tasks.register('prepareDebuggerDebuggerWorkDir') { } } -tasks.register('debugifyLaunchPrograms', JavaExec) { +tasks.register('stageRuntimeClasses', Sync) { dependsOn tasks.named('compileJava') - classpath = sourceSets.main.runtimeClasspath + from compiledMainClassesDirs + into pristineRuntimeClassesDir +} + +def debugifyLaunchPrograms = tasks.register('debugifyLaunchPrograms', JavaExec) { + dependsOn tasks.named('stageRuntimeClasses') + classpath = files(instrumentedRuntimeClassesDir) + configurations.runtimeClasspath mainClass = 'com.lambda.Debugger.Debugify' + workingDir = runtimeInstrumentationWorkDir.get().asFile + + inputs.dir(pristineRuntimeClassesDir) + inputs.file('.debuggerDefaults') + inputs.property('debugifyClassPatterns', debugifyClassPatterns) + outputs.dir(instrumentedRuntimeClassesDir) + outputs.dir(runtimeInstrumentationWorkDir) def debugifyOutput = new ByteArrayOutputStream() standardOutput = debugifyOutput errorOutput = debugifyOutput doFirst { - def dir = file("${layout.buildDirectory.get()}/classes/java/main/com/lambda/Debugger") + def classesDir = instrumentedRuntimeClassesDir.get().asFile + delete classesDir + copy { + from pristineRuntimeClassesDir + into classesDir + } + def workDir = runtimeInstrumentationWorkDir.get().asFile + delete workDir + workDir.mkdirs() + copy { + from '.debuggerDefaults' + into workDir + } + def dir = new File(classesDir, 'com/lambda/Debugger') args = launchProgramClasses(dir)*.absolutePath if (args.isEmpty()) { throw new GradleException("No launch program classes matched for debugification in ${dir}") @@ -159,6 +192,7 @@ tasks.register('debugifyLaunchPrograms', JavaExec) { } } } +sourceSets.main.output.classesDirs.builtBy(debugifyLaunchPrograms) tasks.register('debugifyDebuggerDebuggerLaunchPrograms', JavaExec) { description = 'Pre-instrument launch programs in the package-rewritten ODB' @@ -196,7 +230,7 @@ tasks.register('debugifyDebuggerDebuggerLaunchPrograms', JavaExec) { tasks.register('copyMainClassesForDebuggerDebugger', Sync) { dependsOn tasks.named('debugifyLaunchPrograms') - from sourceSets.main.output.classesDirs + from instrumentedRuntimeClassesDir into debuggerDebuggerMainClassesDir } @@ -244,17 +278,26 @@ tasks.register('debugifyDebuggerDebuggerMainClasses', JavaExec) { tasks.named('jar', Jar) { dependsOn tasks.named('debugifyLaunchPrograms') + dependsOn tasks.named('processResources') archiveFileName = 'LewisOmniscientDebugger.jar' manifest.attributes(manifestAttributes) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + duplicatesStrategy = DuplicatesStrategy.FAIL from { - configurations.runtimeClasspath.collect { dep -> - dep.isDirectory() ? dep : zipTree(dep) - } + configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts + .sort { artifact -> + def id = artifact.moduleVersion.id + "${id.group}:${artifact.name}:${id.version}".toString() + } + .collect { zipTree(it.file) } } - + exclude 'com/lambda/tests/**' + exclude 'edu/insa/LSD/Test*.class' + exclude 'module-info.class' + exclude 'META-INF/versions/**' + exclude 'META-INF/LICENSE*' + exclude 'META-INF/NOTICE*' exclude 'META-INF/MANIFEST.MF' exclude 'META-INF/*.SF' exclude 'META-INF/*.DSA' @@ -269,17 +312,26 @@ tasks.register('debuggerDebuggerJar', Jar) { archiveFileName = 'LewisOmniscientDebugger-debugger-debugger.jar' manifest.attributes(manifestAttributes + ['Main-Class': 'lambda.Debugger.Debugger']) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + duplicatesStrategy = DuplicatesStrategy.FAIL from(debuggerDebuggerMainClassesDir) from(sourceSets.debuggerDebugger.output) from(sourceSets.main.output.resourcesDir) from { - configurations.runtimeClasspath.collect { dep -> - dep.isDirectory() ? dep : zipTree(dep) - } + configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts + .sort { artifact -> + def id = artifact.moduleVersion.id + "${id.group}:${artifact.name}:${id.version}".toString() + } + .collect { zipTree(it.file) } } + exclude 'com/lambda/tests/**' + exclude 'edu/insa/LSD/Test*.class' + exclude 'module-info.class' + exclude 'META-INF/versions/**' + exclude 'META-INF/LICENSE*' + exclude 'META-INF/NOTICE*' exclude 'META-INF/MANIFEST.MF' exclude 'META-INF/*.SF' exclude 'META-INF/*.DSA' @@ -308,3 +360,19 @@ tasks.register('verifyDemoRecordings', Test) { testClassesDirs = sourceSets.test.output.classesDirs include '**/VerifyRecordingTest.class' } + +tasks.register('verifyRuntimeTests', Test) { + description = 'Run finite ODB unit and adapter tests against the runtime JAR' + group = 'verification' + dependsOn tasks.named('jar') + dependsOn tasks.named('testClasses') + + def jarFile = tasks.named('jar', Jar).get().archiveFile + def mainClasses = sourceSets.main.output.classesDirs.files + classpath = sourceSets.test.runtimeClasspath.filter { !mainClasses.contains(it) } + files(jarFile) + testClassesDirs = sourceSets.test.output.classesDirs + exclude '**/VerifyRecordingTest.class' +} + +apply from: 'gradle/collection-provenance.gradle' +apply from: 'gradle/reproducible-release.gradle' diff --git a/docs/legal/COLLECTION-PROVENANCE.md b/docs/legal/COLLECTION-PROVENANCE.md new file mode 100644 index 0000000..53086c6 --- /dev/null +++ b/docs/legal/COLLECTION-PROVENANCE.md @@ -0,0 +1,17 @@ +# Collection compatibility provenance + +Lewis ODB commit `48bbb7dfaf31b129906056b422c7b97f5dde77e0` +replaced the former collection implementation bodies with original +compatibility implementations based only on public Java 8 collection APIs. +The retained class names preserve ODB callers and serialized data. + +`MyArrayList` remains ODB instrumentation machinery. `HashMapEq` preserves +identity-key behavior. `VectorD` preserves identity search and its +non-traversing debugger string while using `java.util.Vector` storage. + +The historical prebuilt `LewisOmniscientDebugger-1.5.jar` was removed in +commit `0ffedc6`. `verifyCollectionProvenance` rejects that binary, former +proprietary notice text, or missing compatibility classes. + +These changes are modifications under the repository's GPL terms. This record +describes project provenance and is not legal advice. diff --git a/gradle.lockfile b/gradle.lockfile new file mode 100644 index 0000000..6d15806 --- /dev/null +++ b/gradle.lockfile @@ -0,0 +1,8 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +commons-io:commons-io:2.21.0=runtimeClasspath +org.apache.bcel:bcel:6.12.0=runtimeClasspath +org.apache.commons:commons-lang3:3.20.0=runtimeClasspath +org.ow2.asm:asm:9.7.1=runtimeClasspath +empty= diff --git a/gradle/canonical-build.properties b/gradle/canonical-build.properties new file mode 100644 index 0000000..b53edba --- /dev/null +++ b/gradle/canonical-build.properties @@ -0,0 +1,10 @@ +container.image=azul/zulu-openjdk:8u492@sha256:185e7ab12b3706a39a56b2d5c8921f36ddff8e1c95d704a0a2f22b7e35c611d1 +environment.LC_ALL=C.UTF-8 +environment.TZ=UTC +file.encoding=UTF-8 +gradle.version=8.12.1 +java.runtime.version=1.8.0_492-b09 +java.vendor=Azul Systems, Inc. +java.version=1.8.0_492 +os.arch=amd64 +os.name=Linux diff --git a/gradle/collection-provenance.gradle b/gradle/collection-provenance.gradle new file mode 100644 index 0000000..25e047c --- /dev/null +++ b/gradle/collection-provenance.gradle @@ -0,0 +1,135 @@ +def arrayListHistoryClassesDir = layout.buildDirectory.dir('verification/array-list-history/classes') +def debuggerDebuggerGeneratedSourceDir = tasks.named('generateDebuggerDebuggerSources').map { + it.outputs.files.singleFile +} +tasks.register('prepareArrayListHistoryTarget', Sync) { + dependsOn tasks.named('testClasses') + from(sourceSets.test.output.classesDirs) { + include 'outside/ArrayListHistoryTarget.class' + } + into(arrayListHistoryClassesDir) +} + +tasks.register('debugifyArrayListHistoryTarget', JavaExec) { + description = 'Pre-instrument the ArrayList history verification target' + group = 'verification' + dependsOn tasks.named('prepareArrayListHistoryTarget') + classpath = sourceSets.test.runtimeClasspath + mainClass = 'com.lambda.Debugger.Debugify' + + def debugifyOutput = new ByteArrayOutputStream() + standardOutput = debugifyOutput + errorOutput = debugifyOutput + + doFirst { + args = [new File(arrayListHistoryClassesDir.get().asFile, + 'outside/ArrayListHistoryTarget.class').absolutePath] + } + doLast { + def output = debugifyOutput.toString('UTF-8') + if (!output.isBlank()) { + logger.lifecycle(output.trim()) + } + if (executionResult.get().exitValue != 0 || !(output =~ /debugified 1 files\./).find()) { + throw new GradleException('Failed to instrument ArrayList history verification target') + } + } +} + +tasks.named('verifyDemoRecordings', Test) { + dependsOn tasks.named('debugifyArrayListHistoryTarget') + + def jarFile = tasks.named('jar', Jar).get().archiveFile + def mainClasses = sourceSets.main.output.classesDirs.files + classpath = files(arrayListHistoryClassesDir) + + sourceSets.test.runtimeClasspath.filter { !mainClasses.contains(it) } + + files(jarFile) +} + +tasks.register('verifyCollectionProvenance') { + description = 'Verify retained ODB collection compatibility classes and release exclusions' + group = 'verification' + dependsOn tasks.named('jar') + dependsOn tasks.named('compileDebuggerDebuggerJava') + dependsOn tasks.named('debuggerDebuggerJar') + + doLast { + def expectedClasses = [ + 'com/lambda/Debugger/HashMapEq.class', + 'com/lambda/Debugger/MyAbstractCollection.class', + 'com/lambda/Debugger/MyAbstractList.class', + 'com/lambda/Debugger/MyArrayList.class', + 'com/lambda/Debugger/MyCollections.class', + 'com/lambda/Debugger/SubList.class', + 'com/lambda/Debugger/VectorD.class', + ] + def jarChecks = [ + [ + file: tasks.named('jar', Jar).get().archiveFile.get().asFile, + classes: expectedClasses, + ], + [ + file: tasks.named('debuggerDebuggerJar', Jar).get().archiveFile.get().asFile, + classes: expectedClasses + expectedClasses.collect { + it.replace('com/lambda/Debugger/', 'lambda/Debugger/') + }, + ], + ] + jarChecks.each { check -> + def jarFile = check.file + def zip = new java.util.zip.ZipFile(jarFile) + try { + check.classes.each { entry -> + if (zip.getEntry(entry) == null) { + throw new GradleException("${jarFile.name} lacks retained compatibility class ${entry}") + } + } + if (zip.entries().find { it.name.endsWith('LewisOmniscientDebugger-1.5.jar') }) { + throw new GradleException("${jarFile.name} embeds the historical binary") + } + } finally { + zip.close() + } + } + + def legacyJar = file('LewisOmniscientDebugger-1.5.jar') + if (legacyJar.exists()) { + throw new GradleException('Historical JAR must not remain in the source tree') + } + + def rewrittenNames = [ + 'HashMapEq.java', + 'VectorD.java', + 'MyAbstractCollection.java', + 'MyAbstractList.java', + 'MyCollections.java', + ] + def forbiddenPhrases = [ + 'Sun Microsystems', + 'confidential and proprietary information', + 'Use is subject to license terms', + ] + def sourceRoots = [ + file('src/main/java/com/lambda/Debugger'), + debuggerDebuggerGeneratedSourceDir.get().toPath() + .resolve('lambda/Debugger').toFile(), + ] + sourceRoots.each { root -> + rewrittenNames.each { name -> + def source = new File(root, name) + if (!source.isFile()) { + throw new GradleException("Missing retained collection source ${source}") + } + forbiddenPhrases.each { phrase -> + if (source.getText('UTF-8').contains(phrase)) { + throw new GradleException("${source} retains prohibited copied notice text: ${phrase}") + } + } + } + } + } +} + +tasks.named('check') { + dependsOn tasks.named('verifyCollectionProvenance') +} diff --git a/gradle/reproducible-release.gradle b/gradle/reproducible-release.gradle new file mode 100644 index 0000000..c5690c2 --- /dev/null +++ b/gradle/reproducible-release.gradle @@ -0,0 +1,389 @@ +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.api.tasks.bundling.Compression + +def configureReproducibleArchive = { AbstractArchiveTask archiveTask -> + archiveTask.preserveFileTimestamps = false + archiveTask.reproducibleFileOrder = true + archiveTask.filePermissions { + unix('rw-r--r--') + } + archiveTask.dirPermissions { + unix('rwxr-xr-x') + } +} + +configureReproducibleArchive(tasks.named('jar', Jar).get()) +configureReproducibleArchive(tasks.named('debuggerDebuggerJar', Jar).get()) + +def injectedSourceCommit = providers.gradleProperty('odbSourceCommit') +def sourceCommit = { + def marker = file('SOURCE-COMMIT') + def value + if (injectedSourceCommit.isPresent()) { + value = injectedSourceCommit.get() + } else if (marker.isFile()) { + value = marker.getText('UTF-8').trim() + } else { + def output = new ByteArrayOutputStream() + exec { + commandLine 'git', 'rev-parse', 'HEAD' + standardOutput = output + } + value = output.toString('UTF-8').trim() + } + if (!(value ==~ /[0-9a-f]{40}/)) { + throw new GradleException("Invalid source commit: ${value}") + } + value +} +def sha256 = { File artifact -> + def digest = java.security.MessageDigest.getInstance('SHA-256') + artifact.withInputStream { input -> + def buffer = new byte[8192] + for (int read = input.read(buffer); read != -1; read = input.read(buffer)) { + digest.update(buffer, 0, read) + } + } + digest.digest().encodeHex().toString() +} +def sourceCommitFile = layout.buildDirectory.file('generated/release/SOURCE-COMMIT') +def generateSourceCommit = tasks.register('generateSourceCommit') { + inputs.property('sourceCommit', providers.provider { sourceCommit() }) + outputs.file(sourceCommitFile) + doLast { + def output = sourceCommitFile.get().asFile + output.parentFile.mkdirs() + output.setText(sourceCommit() + '\n', 'UTF-8') + } +} +def sourceArchiveFile = tasks.register('sourceArchive', Tar) { + description = 'Build deterministic corresponding source for the ODB runtime' + group = 'distribution' + compression = Compression.GZIP + destinationDirectory = layout.buildDirectory.dir('distributions') + archiveFileName = providers.provider { "odb-source-${sourceCommit()}.tar.gz" } + configureReproducibleArchive(delegate) + + dependsOn 'verifyReleaseCheckout' + dependsOn generateSourceCommit + + from(rootDir) { + include '.debuggerDefaults' + include 'COPYING' + include 'README.md' + include 'build.gradle' + include 'settings.gradle' + include 'gradle.lockfile' + include 'gradlew.bat' + include 'gradle/**' + include 'src/**' + include 'docs/legal/**' + include 'verification/**' + } + from('scripts/verify-canonical-release.sh') { + into 'scripts' + filePermissions { + unix('rwxr-xr-x') + } + } + from('gradlew') { + filePermissions { + unix('rwxr-xr-x') + } + } + from(sourceCommitFile) +} + +tasks.register('verifyReleaseCheckout') { + description = 'Require a clean Git checkout for release artifacts' + group = 'verification' + + doLast { + if (file('.git').exists()) { + def output = new ByteArrayOutputStream() + exec { + commandLine 'git', 'status', '--porcelain', '--untracked-files=all' + standardOutput = output + } + if (!output.toString('UTF-8').trim().isEmpty()) { + throw new GradleException('Release artifacts require a clean worktree') + } + } else if (!injectedSourceCommit.isPresent()) { + throw new GradleException( + 'Release artifacts require a clean Git checkout or canonical source snapshot') + } + sourceCommit() + } +} + +tasks.register('verifySourceArchive') { + description = 'Verify corresponding-source contents and exclusions' + group = 'verification' + dependsOn sourceArchiveFile + + doLast { + def archive = sourceArchiveFile.get().archiveFile.get().asFile + def tree = tarTree(resources.gzip(archive)) + def relativeNames = [] + tree.visit { details -> + if (!details.directory) { + relativeNames.add(details.relativePath.pathString) + } + } + def required = [ + '.debuggerDefaults', + 'COPYING', + 'SOURCE-COMMIT', + 'build.gradle', + 'settings.gradle', + 'gradle.lockfile', + 'gradlew', + 'gradlew.bat', + 'gradle/verification-metadata.xml', + 'gradle/wrapper/gradle-wrapper.jar', + 'gradle/wrapper/gradle-wrapper.properties', + 'scripts/verify-canonical-release.sh', + 'src/main/java/com/lambda/Debugger/IntegrationLauncher.java', + 'src/test/java/com/lambda/Debugger/IntegrationLauncherProcessTest.java', + 'docs/legal/COLLECTION-PROVENANCE.md', + 'verification/odb-runtime-osv.json', + ] + required.each { name -> + if (!relativeNames.contains(name)) { + throw new GradleException("Source archive lacks ${name}") + } + } + def forbidden = relativeNames.find { name -> + name.startsWith('build/') + || name.startsWith('.git/') + || name.startsWith('.gradle/') + || name.endsWith('LewisOmniscientDebugger-1.5.jar') + || (name.endsWith('.jar') && name != 'gradle/wrapper/gradle-wrapper.jar') + } + if (forbidden != null) { + throw new GradleException("Source archive contains forbidden entry ${forbidden}") + } + def commitEntry = tree.matching { include 'SOURCE-COMMIT' }.singleFile + if (commitEntry.getText('UTF-8').trim() != sourceCommit()) { + throw new GradleException('Source archive commit marker is incorrect') + } + } +} + +def releaseMetadataFile = layout.buildDirectory.file('generated/release/odb-release.json') +def runtimeSbomFile = layout.buildDirectory.file('reports/odb-runtime.cdx.json') +def vulnerabilityEvidence = file('verification/odb-runtime-osv.json') +def canonicalEnvironment = file('gradle/canonical-build.properties') +def collectionProvenance = file('docs/legal/COLLECTION-PROVENANCE.md') + +tasks.register('generateReleaseMetadata') { + description = 'Generate deterministic ODB release metadata' + group = 'distribution' + dependsOn tasks.named('jar') + dependsOn sourceArchiveFile + dependsOn tasks.named('generateRuntimeSbom') + inputs.file(tasks.named('jar', Jar).flatMap { it.archiveFile }) + inputs.file(sourceArchiveFile.flatMap { it.archiveFile }) + inputs.file(runtimeSbomFile) + inputs.file(vulnerabilityEvidence) + inputs.file(canonicalEnvironment) + inputs.property('sourceCommit', providers.provider { sourceCommit() }) + outputs.file(releaseMetadataFile) + + doLast { + def runtime = tasks.named('jar', Jar).get().archiveFile.get().asFile + def source = sourceArchiveFile.get().archiveFile.get().asFile + def sbom = runtimeSbomFile.get().asFile + def environment = new Properties() + canonicalEnvironment.withInputStream { environment.load(it) } + def environmentValues = new TreeMap() + environment.each { key, value -> environmentValues[key.toString()] = value.toString() } + def dependencies = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.collect { + def id = it.moduleVersion.id + "${id.group}:${it.name}:${id.version}".toString() + }.sort() + def metadata = [ + sourceCommit : sourceCommit(), + runtime : [name: runtime.name, sha256: sha256(runtime)], + source : [name: source.name, sha256: sha256(source)], + javaClassVersion : 52, + integrationProtocol : 1, + dependencies : dependencies, + sbomSha256 : sha256(sbom), + vulnerabilityEvidence : [ + name : vulnerabilityEvidence.name, + sha256 : sha256(vulnerabilityEvidence), + ], + canonicalBuildEnvironment: environmentValues, + buildCommand : './scripts/verify-canonical-release.sh', + ] + def output = releaseMetadataFile.get().asFile + output.parentFile.mkdirs() + output.setText( + groovy.json.JsonOutput.prettyPrint( + groovy.json.JsonOutput.toJson(metadata)) + '\n', + 'UTF-8') + } +} + +def checksumsFile = layout.buildDirectory.file('generated/release/SHA256SUMS') +tasks.register('generateReleaseChecksums') { + description = 'Generate sorted checksums for ODB release inputs' + group = 'distribution' + dependsOn tasks.named('generateReleaseMetadata') + dependsOn tasks.named('prepareRuntimeLegal') + inputs.file(tasks.named('jar', Jar).flatMap { it.archiveFile }) + inputs.file(sourceArchiveFile.flatMap { it.archiveFile }) + inputs.file(releaseMetadataFile) + inputs.file(runtimeSbomFile) + inputs.file(vulnerabilityEvidence) + inputs.file('COPYING') + inputs.file(collectionProvenance) + inputs.dir(layout.buildDirectory.dir('generated/runtime-legal')) + inputs.file('src/main/resources/META-INF/third-party/asm/LICENSE.txt') + outputs.file(checksumsFile) + + doLast { + def artifacts = [ + [name: tasks.named('jar', Jar).get().archiveFile.get().asFile.name, + file: tasks.named('jar', Jar).get().archiveFile.get().asFile], + [name: sourceArchiveFile.get().archiveFile.get().asFile.name, + file: sourceArchiveFile.get().archiveFile.get().asFile], + [name: 'odb-release.json', file: releaseMetadataFile.get().asFile], + [name: 'odb-runtime.cdx.json', file: runtimeSbomFile.get().asFile], + [name: 'odb-runtime-osv.json', file: vulnerabilityEvidence], + [name: 'COPYING', file: file('COPYING')], + [name: 'COLLECTION-PROVENANCE.md', file: collectionProvenance], + ] + def runtimeLegal = layout.buildDirectory.dir('generated/runtime-legal').get().asFile + fileTree(runtimeLegal).files.sort { it.absolutePath }.each { legal -> + artifacts.add([ + name: "third-party/${runtimeLegal.toPath().relativize(legal.toPath())}".replace('\\', '/'), + file: legal, + ]) + } + artifacts.add([ + name: 'third-party/asm/LICENSE.txt', + file: file('src/main/resources/META-INF/third-party/asm/LICENSE.txt'), + ]) + def output = checksumsFile.get().asFile + output.parentFile.mkdirs() + output.setText(artifacts.sort { it.name }.collect { + "${sha256(it.file)} ${it.name}" + }.join('\n') + '\n', 'UTF-8') + } +} + +tasks.register('releaseBundle', Sync) { + description = 'Collect the reproducible ODB runtime and release evidence' + group = 'distribution' + dependsOn tasks.named('verifyReleaseCheckout') + dependsOn tasks.named('generateReleaseChecksums') + into layout.buildDirectory.dir('release') + + from(tasks.named('jar', Jar).map { it.archiveFile }) + from(sourceArchiveFile.map { it.archiveFile }) + from(releaseMetadataFile) + from(checksumsFile) + from(runtimeSbomFile) + from(vulnerabilityEvidence) + from('COPYING') + from(collectionProvenance) + from(layout.buildDirectory.dir('generated/runtime-legal')) { + into 'third-party' + } + from('src/main/resources/META-INF/third-party/asm/LICENSE.txt') { + into 'third-party/asm' + } +} + +def rebuildRoots = [ + layout.buildDirectory.dir('verification/rebuild-one'), + layout.buildDirectory.dir('verification/rebuild-two'), +] +def rebuildTasks = rebuildRoots.withIndex().collect { rebuildRoot, index -> + def extractTask = tasks.register("extractSourceArchive${index + 1}", Sync) { + dependsOn sourceArchiveFile + from { + tarTree(resources.gzip(sourceArchiveFile.get().archiveFile.get().asFile)) + } + into rebuildRoot + } + tasks.register("buildSourceArchive${index + 1}", Exec) { + dependsOn extractTask + inputs.file(sourceArchiveFile.flatMap { it.archiveFile }) + workingDir rebuildRoot + commandLine './gradlew', '--offline', '--no-daemon', '--no-watch-fs', + 'clean', 'jar', 'verifyRuntimeArtifact' + outputs.file(rebuildRoot.map { it.file('build/libs/LewisOmniscientDebugger.jar') }) + } +} + +tasks.register('verifyReproducibleRuntime') { + description = 'Compare two isolated source-archive rebuilds with the runtime JAR' + group = 'verification' + dependsOn tasks.named('jar') + dependsOn tasks.named('verifySourceArchive') + dependsOn rebuildTasks + + doLast { + def expected = tasks.named('jar', Jar).get().archiveFile.get().asFile + def expectedSha = sha256(expected) + rebuildRoots.each { root -> + def rebuilt = root.get().file('build/libs/LewisOmniscientDebugger.jar').asFile + def rebuiltSha = sha256(rebuilt) + if (rebuiltSha != expectedSha) { + throw new GradleException( + "Source rebuild digest ${rebuiltSha} differs from ${expectedSha}") + } + } + } +} + +tasks.register('verifyCanonicalBuildEnvironment') { + description = 'Verify the pinned Linux and JDK 8 release environment' + group = 'verification' + inputs.file(canonicalEnvironment) + + doLast { + def expected = new Properties() + canonicalEnvironment.withInputStream { expected.load(it) } + def actual = [ + 'os.name' : System.getProperty('os.name'), + 'os.arch' : System.getProperty('os.arch'), + 'java.version' : System.getProperty('java.version'), + 'java.runtime.version': System.getProperty('java.runtime.version'), + 'java.vendor' : System.getProperty('java.vendor'), + 'file.encoding' : System.getProperty('file.encoding'), + 'gradle.version' : gradle.gradleVersion, + ] + actual.each { key, value -> + if (value != expected.getProperty(key)) { + throw new GradleException( + "Canonical build requires ${key}=${expected.getProperty(key)}, found ${value}") + } + } + ['LC_ALL', 'TZ'].each { name -> + def key = "environment.${name}" + if (System.getenv(name) != expected.getProperty(key)) { + throw new GradleException( + "Canonical build requires ${name}=${expected.getProperty(key)}, " + + "found ${System.getenv(name)}") + } + } + } +} + +tasks.register('verifyCanonicalRelease') { + description = 'Run finite canonical ODB release gates' + group = 'verification' + dependsOn tasks.named('verifyCanonicalBuildEnvironment') + dependsOn tasks.named('verifyReproducibleRuntime') + dependsOn tasks.named('verifyRuntimeDependencies') + dependsOn tasks.named('verifyRuntimeArtifact') + dependsOn tasks.named('verifyCollectionProvenance') + dependsOn tasks.named('test') + dependsOn tasks.named('verifyRuntimeTests') + dependsOn tasks.named('verifyDemoRecordings') + dependsOn tasks.named('releaseBundle') +} diff --git a/gradle/runtime-provenance.gradle b/gradle/runtime-provenance.gradle new file mode 100644 index 0000000..058988c --- /dev/null +++ b/gradle/runtime-provenance.gradle @@ -0,0 +1,334 @@ +configurations.runtimeClasspath.resolutionStrategy.activateDependencyLocking() + +def runtimeComponents = [ + [ + group : 'commons-io', + name : 'commons-io', + probeClass : 'org/apache/commons/io/IOUtils.class', + license : 'Apache-2.0', + directory : 'commons-io', + ], + [ + group : 'org.apache.bcel', + name : 'bcel', + probeClass : 'org/apache/bcel/Const.class', + license : 'Apache-2.0', + directory : 'bcel', + ], + [ + group : 'org.apache.commons', + name : 'commons-lang3', + probeClass : 'org/apache/commons/lang3/StringUtils.class', + license : 'Apache-2.0', + directory : 'commons-lang3', + ], + [ + group : 'org.ow2.asm', + name : 'asm', + probeClass : 'org/objectweb/asm/ClassReader.class', + license : 'BSD-3-Clause', + ], +] +def runtimeComponentKeys = runtimeComponents.collect { "${it.group}:${it.name}".toString() }.sort() +def runtimeComponentByKey = runtimeComponents.collectEntries { [ + ("${it.group}:${it.name}".toString()): it, +] } +def runtimeCoordinate = { artifact -> + def id = artifact.moduleVersion.id + "${id.group}:${artifact.name}:${id.version}".toString() +} +def runtimeSbomFile = layout.buildDirectory.file('reports/odb-runtime.cdx.json') +def runtimeLegalDir = layout.buildDirectory.dir('generated/runtime-legal') +def sha256 = { File artifact -> + def digest = java.security.MessageDigest.getInstance('SHA-256') + artifact.withInputStream { input -> + def buffer = new byte[8192] + for (int read = input.read(buffer); read != -1; read = input.read(buffer)) { + digest.update(buffer, 0, read) + } + } + digest.digest().encodeHex().toString() +} +def sbomCoordinates = { sbom -> + sbom.components.collect { + it.properties.find { property -> property.name == 'lewisodb:coordinate' }.value + }.sort() +} + +tasks.register('generateRuntimeSbom') { + description = 'Generate the CycloneDX inventory for the ODB runtime graph' + group = 'build' + inputs.files(configurations.runtimeClasspath) + inputs.property('runtimeComponents', runtimeComponents) + outputs.file(runtimeSbomFile) + + doLast { + def components = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.collect { artifact -> + def id = artifact.moduleVersion.id + def key = "${id.group}:${artifact.name}".toString() + def component = runtimeComponentByKey[key] + if (component == null) { + throw new GradleException("Unapproved runtime dependency ${runtimeCoordinate(artifact)}") + } + [ + type : 'library', + group : id.group, + name : artifact.name, + version : id.version, + purl : "pkg:maven/${id.group}/${artifact.name}@${id.version}", + hashes : [[alg: 'SHA-256', content: sha256(artifact.file)]], + licenses : [[license: [id: component.license]]], + properties : [[name: 'lewisodb:coordinate', value: runtimeCoordinate(artifact)]], + ] + }.sort { it.properties[0].value } + + def bom = [ + bomFormat : 'CycloneDX', + specVersion : '1.5', + version : 1, + metadata : [component: [ + type : 'application', + group : project.group.toString(), + name : base.archivesName.get(), + version : project.version.toString(), + ]], + components : components, + ] + def output = runtimeSbomFile.get().asFile + output.parentFile.mkdirs() + output.setText( + groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(bom)) + '\n', + 'UTF-8') + } +} + +tasks.register('prepareRuntimeLegal') { + description = 'Collect dependency licenses and notices for the ODB runtime JAR' + group = 'build' + inputs.files(configurations.runtimeClasspath) + inputs.property('runtimeComponents', runtimeComponents) + outputs.dir(runtimeLegalDir) + + doLast { + def output = runtimeLegalDir.get().asFile + delete output + def artifacts = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts + runtimeComponents.findAll { it.directory }.each { component -> + def artifact = artifacts.find { + def id = it.moduleVersion.id + id.group == component.group && it.name == component.name + } + if (artifact == null) { + throw new GradleException("Runtime dependency ${component.group}:${component.name} is missing") + } + def zip = new java.util.zip.ZipFile(artifact.file) + try { + ['LICENSE.txt', 'NOTICE.txt'].each { fileName -> + def entry = zip.getEntry("META-INF/${fileName}") + if (entry == null) { + throw new GradleException("${component.group}:${component.name} lacks META-INF/${fileName}") + } + def target = new File(output, "${component.directory}/${fileName}") + target.parentFile.mkdirs() + zip.getInputStream(entry).withCloseable { input -> + target.withOutputStream { stream -> stream << input } + } + } + } finally { + zip.close() + } + } + } +} + +tasks.register('verifyRuntimeDependencies') { + description = 'Verify the locked ODB runtime dependency graph and Java 8 compatibility' + group = 'verification' + dependsOn tasks.named('generateRuntimeSbom') + inputs.file('verification/odb-runtime-osv.json') + + doLast { + def artifacts = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts + def resolvedKeys = artifacts.collect { + "${it.moduleVersion.id.group}:${it.name}".toString() + }.sort() + if (resolvedKeys != runtimeComponentKeys) { + throw new GradleException("Runtime dependency graph differs from the approved graph: ${resolvedKeys}") + } + + artifacts.each { artifact -> + def key = "${artifact.moduleVersion.id.group}:${artifact.name}".toString() + def component = runtimeComponentByKey[key] + def zip = new java.util.zip.ZipFile(artifact.file) + try { + def entry = zip.getEntry(component.probeClass) + if (entry == null) { + throw new GradleException("${runtimeCoordinate(artifact)} lacks ${component.probeClass}") + } + def input = new DataInputStream(zip.getInputStream(entry)) + try { + if (input.readInt() != -889275714) { + throw new GradleException("${runtimeCoordinate(artifact)} probe is not a class file") + } + input.readUnsignedShort() + def majorVersion = input.readUnsignedShort() + if (majorVersion > 52) { + throw new GradleException( + "${runtimeCoordinate(artifact)} requires class-file version " + + "${majorVersion}, above Java 8") + } + } finally { + input.close() + } + } finally { + zip.close() + } + } + + def expectedCoordinates = artifacts.collect(runtimeCoordinate).sort() + def sbom = new groovy.json.JsonSlurper().parse(runtimeSbomFile.get().asFile) + if (sbom.bomFormat != 'CycloneDX' + || sbom.specVersion != '1.5' + || sbomCoordinates(sbom) != expectedCoordinates) { + throw new GradleException('Generated CycloneDX SBOM differs from the runtime graph') + } + + def vulnerabilityEvidence = new groovy.json.JsonSlurper() + .parse(file('verification/odb-runtime-osv.json')) + def expectedPurls = artifacts.collect { + def id = it.moduleVersion.id + "pkg:maven/${id.group}/${it.name}@${id.version}".toString() + }.sort() + def evidencePurls = vulnerabilityEvidence.packages.collect { it.purl }.sort() + if (vulnerabilityEvidence.schemaVersion != 1 + || evidencePurls != expectedPurls) { + throw new GradleException( + 'OSV evidence differs from the approved runtime graph') + } + def reportedVulnerabilities = vulnerabilityEvidence.packages.collectMany { + it.vulnerabilities ?: [] + } + if (!reportedVulnerabilities.isEmpty()) { + throw new GradleException( + "OSV evidence reports runtime vulnerabilities: ${reportedVulnerabilities}") + } + } +} + +tasks.register('verifyRuntimeArtifact') { + description = 'Verify runtime inventory and legal files in the ODB fat JAR' + group = 'verification' + dependsOn tasks.named('jar') + + doLast { + def jarFile = tasks.named('jar', Jar).get().archiveFile.get().asFile + def expectedEntries = [ + 'META-INF/odb-runtime.cdx.json', + 'META-INF/third-party/asm/LICENSE.txt', + 'META-INF/third-party/bcel/LICENSE.txt', + 'META-INF/third-party/bcel/NOTICE.txt', + 'META-INF/third-party/commons-io/LICENSE.txt', + 'META-INF/third-party/commons-io/NOTICE.txt', + 'META-INF/third-party/commons-lang3/LICENSE.txt', + 'META-INF/third-party/commons-lang3/NOTICE.txt', + ] + def zip = new java.util.zip.ZipFile(jarFile) + try { + def requiredEntries = [ + 'com/lambda/Debugger/Debugger.class', + 'com/lambda/Debugger/IntegrationLauncher.class', + 'com/lambda/Debugger/IntegrationState.class', + ] + requiredEntries.each { entry -> + def requiredEntry = zip.getEntry(entry) + if (requiredEntry == null) { + throw new GradleException("Runtime JAR lacks ${entry}") + } + def requiredInput = new DataInputStream(zip.getInputStream(requiredEntry)) + try { + requiredInput.readInt() + requiredInput.readUnsignedShort() + if (requiredInput.readUnsignedShort() != 52) { + throw new GradleException( + "${entry} is not compiled for Java 8 class version 52") + } + } finally { + requiredInput.close() + } + } + expectedEntries.each { entry -> + if (zip.getEntry(entry) == null) { + throw new GradleException("Runtime JAR lacks ${entry}") + } + } + def forbiddenEntryPatterns = [ + ~/^com\/lambda\/tests\//, + ~/^outside\//, + ~/^org\/junit\//, + ~/^META-INF\/versions\//, + ~/(^|\/)module-info\.class$/, + ~/\.(so|dll|dylib|jnilib)$/, + ~/LewisOmniscientDebugger-1\.5\.jar$/, + ] + zip.entries().each { entry -> + if (forbiddenEntryPatterns.any { pattern -> + pattern.matcher(entry.name).find() + }) { + throw new GradleException("Runtime JAR contains forbidden entry ${entry.name}") + } + if (entry.name.endsWith('.class')) { + def classInput = new DataInputStream(zip.getInputStream(entry)) + try { + if (classInput.readInt() != -889275714) { + throw new GradleException("${entry.name} is not a valid class file") + } + classInput.readUnsignedShort() + def majorVersion = classInput.readUnsignedShort() + if (majorVersion > 52) { + throw new GradleException( + "${entry.name} uses class-file version ${majorVersion}, " + + 'above Java 8') + } + } finally { + classInput.close() + } + } + } + def input = zip.getInputStream(zip.getEntry('META-INF/odb-runtime.cdx.json')) + try { + def sbom = new groovy.json.JsonSlurper().parse(input) + def expectedCoordinates = configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts + .collect(runtimeCoordinate).sort() + if (sbomCoordinates(sbom) != expectedCoordinates) { + throw new GradleException('Runtime JAR SBOM differs from the runtime graph') + } + } finally { + input.close() + } + } finally { + zip.close() + } + } +} + +def addRuntimeMetadataToJar = { Jar jarTask -> + jarTask.dependsOn tasks.named('generateRuntimeSbom') + jarTask.dependsOn tasks.named('prepareRuntimeLegal') + jarTask.from(runtimeSbomFile) { + into 'META-INF' + } + jarTask.from(runtimeLegalDir) { + into 'META-INF/third-party' + } +} + +tasks.withType(Jar).configureEach { jarTask -> + if (jarTask.name in ['jar', 'debuggerDebuggerJar']) { + addRuntimeMetadataToJar(jarTask) + } +} + +tasks.named('check') { + dependsOn tasks.named('verifyRuntimeDependencies') + dependsOn tasks.named('verifyRuntimeArtifact') +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 0000000..a89ee21 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,108 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/verify-canonical-release.sh b/scripts/verify-canonical-release.sh new file mode 100755 index 0000000..7eb2a1c --- /dev/null +++ b/scripts/verify-canonical-release.sh @@ -0,0 +1,74 @@ +#!/bin/sh +set -eu + +script_directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +project_directory=$(dirname -- "$script_directory") +canonical_properties="$project_directory/gradle/canonical-build.properties" +container_image=$(sed -n 's/^container\.image=//p' "$canonical_properties") + +if [ -z "$container_image" ]; then + echo "Canonical container image is missing from $canonical_properties." >&2 + exit 2 +fi + +snapshot_checkout=false +if git -C "$project_directory" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + if [ -n "$(git -C "$project_directory" status --porcelain --untracked-files=all)" ]; then + echo "Canonical release verification requires a clean worktree." >&2 + exit 2 + fi + source_commit=$(git -C "$project_directory" rev-parse HEAD) + snapshot_checkout=true +elif [ -f "$project_directory/SOURCE-COMMIT" ]; then + source_commit=$(tr -d '\r\n' < "$project_directory/SOURCE-COMMIT") +else + echo "Canonical release verification requires a Git checkout or source archive." >&2 + exit 2 +fi +case "$source_commit" in + *[!0-9a-f]*|'') + echo "Invalid source commit: $source_commit" >&2 + exit 2 + ;; +esac +if [ "${#source_commit}" -ne 40 ]; then + echo "Invalid source commit: $source_commit" >&2 + exit 2 +fi + +container_uid=$(id -u) +container_gid=$(id -g) +temporary_directory=$(mktemp -d "${TMPDIR:-/tmp}/odb-canonical-release.XXXXXX") +source_directory="$temporary_directory/source" +canonical_output="$project_directory/build/canonical-release" + +cleanup() { + chmod -R u+w "$temporary_directory" 2>/dev/null || true + rm -rf -- "$temporary_directory" +} +trap cleanup EXIT HUP INT TERM + +if [ "$snapshot_checkout" = true ]; then + mkdir "$source_directory" + git -C "$project_directory" archive --format=tar HEAD | tar -xf - -C "$source_directory" +else + source_directory="$project_directory" +fi + +docker run --rm \ + --platform linux/amd64 \ + --user "$container_uid:$container_gid" \ + --env LC_ALL=C.UTF-8 \ + --env TZ=UTC \ + --env HOME=/tmp/odb-home \ + --env GRADLE_USER_HOME=/tmp/gradle-home \ + --tmpfs "/tmp/odb-home:rw,exec,uid=$container_uid,gid=$container_gid,size=16777216" \ + --tmpfs "/tmp/gradle-home:rw,exec,uid=$container_uid,gid=$container_gid,size=2147483648" \ + --volume "$source_directory:/workspace" \ + --workdir /workspace \ + "$container_image" \ + ./gradlew -PodbSourceCommit="$source_commit" --no-daemon --no-watch-fs clean verifyCanonicalRelease + +rm -rf -- "$canonical_output" +mkdir -p "$canonical_output" +cp -R "$source_directory/build/release/." "$canonical_output/" diff --git a/src/main/java/com/lambda/Debugger/CodePane.java b/src/main/java/com/lambda/Debugger/CodePane.java index 25c98c5..1fe568c 100644 --- a/src/main/java/com/lambda/Debugger/CodePane.java +++ b/src/main/java/com/lambda/Debugger/CodePane.java @@ -90,7 +90,7 @@ public static VectorD getDisplayList(String sourceFileName, String className) { if (Debugger.DEMO) return getDemoList(sourceFileName); - ClassPath.ClassFile cf = Repository.lookupClassFile(className); + ClassPath.ClassFile cf = lookupClassFile(className); if (cf != null) sourceFilePath = getSourceFileName(cf, sourceFileName); else sourceFilePath = getSourceFileName(className, sourceFileName); @@ -101,6 +101,16 @@ public static VectorD getDisplayList(String sourceFileName, String className) { return(buildFileLines(r, sourceFileName)); } + static ClassPath.ClassFile lookupClassFile(String className) { + try { + ClassPath classPath = Repository.getRepository().getClassPath(); + return classPath == null ? null : classPath.getClassFile(className); + } + catch (IOException e) { + return null; + } + } + private static BufferedReader getReader(String sourceFilePath) { // System.out.println("Reading in: " +sourceFilePath); try { @@ -183,4 +193,3 @@ public static void main(String[] args) { System.out.println("Done."); } } - diff --git a/src/main/java/com/lambda/Debugger/D.java b/src/main/java/com/lambda/Debugger/D.java index 5cf1087..7ed6237 100644 --- a/src/main/java/com/lambda/Debugger/D.java +++ b/src/main/java/com/lambda/Debugger/D.java @@ -1354,6 +1354,9 @@ public static synchronized Object createShadowClass(String className) { } catch (ClassNotFoundException e) { Debugger.println("createShadowClass can't find class. IMPOSSIBLE " + className); + if (IntegrationState.isActive()) { + throw IntegrationState.internalFailed("ODB could not create a shadow class.", e); + } System.exit(1); } return null; // Never gets here diff --git a/src/main/java/com/lambda/Debugger/Debugger.java b/src/main/java/com/lambda/Debugger/Debugger.java index 38bd730..d523776 100644 --- a/src/main/java/com/lambda/Debugger/Debugger.java +++ b/src/main/java/com/lambda/Debugger/Debugger.java @@ -30,6 +30,7 @@ import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Date; import javax.swing.AbstractListModel; @@ -1041,6 +1042,63 @@ public static void main2(String args[]) { } // Otherwise only create it when the user pushes STOP (StopButton.java) + static void runIntegration(String target, String[] targetArguments, boolean showController) { + println(version); + CMD_LINE = true; + START = true; + readCommandLineFlags(); + firstRun = Defaults.readDefaults(); + IntegrationState.loadSourceDirectories(); + readCommandLineFlags(); + TimeStamp.initialize(); + programName = target; + classLoader = new DebugifyingClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader); + try { + clazz = classLoader.loadClass(programName); + } catch (ClassNotFoundException error) { + throw IntegrationState.fatal( + "TARGET_CLASS_NOT_FOUND", + "Could not load target class " + programName + ".", + programName, + error.toString(), + 1); + } + if (clazz.getClassLoader() != classLoader) { + throw IntegrationState.instrumentationFailed( + programName, + new IllegalStateException("Target was loaded without ODB instrumentation.")); + } + + Method mainMethod; + try { + mainMethod = clazz.getDeclaredMethod("main", new Class[] { String[].class }); + } catch (Exception error) { + throw invalidIntegrationMain(error.toString()); + } + int modifiers = mainMethod.getModifiers(); + if (!Modifier.isPublic(modifiers) || !Modifier.isStatic(modifiers) + || mainMethod.getReturnType() != Void.TYPE) { + throw invalidIntegrationMain("Expected public static void main(String[])."); + } + + D.enable(); + if (showController) { + applyRecordingStartup(true); + } + IntegrationState.targetLoaded(programName); + runMain(clazz, new Object[] { targetArguments }); + } + + private static Error invalidIntegrationMain(String cause) { + return IntegrationState.fatal( + "MAIN_METHOD_INVALID", + "Target class " + programName + " must declare public static void main(String[]).", + programName, + cause, + 1); + } + private static void runMain(Class clazz, Object[] argList) { runTarget(clazz, argList); if (SHOW) @@ -1052,11 +1110,13 @@ private static void runMain(Class clazz, Object[] argList) { public static synchronized void createDebugger() { if (mainFrame != null) return; + IntegrationState.requireUsefulRecording(TimeStamp.nTSCreated, TimeStamp.eott()); // Somebody already created it. (Early STOP button is one way) mainFrame = new Debugger(); mainFrame.initialize(); mainFrame.pack(); mainFrame.setVisible(true); + IntegrationState.debuggerReady(TimeStamp.nTSCreated, TimeStamp.eott()); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ThreadPane.initialize(); previousTime = TimeStamp.bot1(); @@ -1140,6 +1200,9 @@ static void runTarget(Class c, Object[] a) { method = clazz.getDeclaredMethod("main", new Class[] { String[].class }); } catch (Exception e) { + if (IntegrationState.isActive()) { + throw IntegrationState.internalFailed("Target main method became unavailable.", e); + } System.out.println("There is no main(String[] argv) in " + clazz + ".\n" + e); System.exit(1); @@ -1219,7 +1282,14 @@ static void stopTarget() { return; Runnable r = new Runnable() { public void run() { - createDebugger(); + try { + createDebugger(); + } catch (Throwable error) { + if (IntegrationState.isActive()) { + throw IntegrationState.internalFailed("Could not open the ODB debugger.", error); + } + throw error; + } } }; SwingUtilities.invokeLater(r); diff --git a/src/main/java/com/lambda/Debugger/DebuggerCommand.java b/src/main/java/com/lambda/Debugger/DebuggerCommand.java index a785cdb..f51c168 100644 --- a/src/main/java/com/lambda/Debugger/DebuggerCommand.java +++ b/src/main/java/com/lambda/Debugger/DebuggerCommand.java @@ -370,24 +370,38 @@ public void replayRecordings() { public static void writeHistory() { try { - ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(Debugger.programName+ ".debuggerCommands")); + ObjectOutputStream oos = new ObjectOutputStream( + IntegrationState.commandHistoryOutput(Debugger.programName)); oos.writeObject(commandHistoryList); oos.close(); Debugger.message("Saved history to "+Debugger.programName+ ".debuggerCommands.", false); } - catch (IOException e) {Debugger.message("Couldn't save history " + e, true);} + catch (IOException e) { + if (IntegrationState.isActive()) + throw IntegrationState.stateIoFailed("write command history", e); + Debugger.message("Couldn't save history " + e, true); + } } public static void readHistory() { try { - ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Debugger.programName+ ".debuggerCommands")); + ObjectInputStream ois = new ObjectInputStream( + IntegrationState.commandHistoryInput(Debugger.programName)); commandHistoryList = (DebuggerCommandHistoryList)ois.readObject(); ois.close(); } - catch (IOException e) {Debugger.message("Couldn't read history " + e, true);} - catch (ClassNotFoundException e) {Debugger.message("Couldn't read history " + e, true);} + catch (IOException e) { + if (IntegrationState.isActive()) + throw IntegrationState.stateIoFailed("read command history", e); + Debugger.message("Couldn't read history " + e, true); + } + catch (ClassNotFoundException e) { + if (IntegrationState.isActive()) + throw IntegrationState.stateIoFailed("read command history", e); + Debugger.message("Couldn't read history " + e, true); + } } public static void reset() { diff --git a/src/main/java/com/lambda/Debugger/Debugify.java b/src/main/java/com/lambda/Debugger/Debugify.java index 4155d71..238d1c6 100644 --- a/src/main/java/com/lambda/Debugger/Debugify.java +++ b/src/main/java/com/lambda/Debugger/Debugify.java @@ -2519,6 +2519,9 @@ public static void replacePatch(InstructionHandle ih, String debug) { } catch (TargetLostException e) { Debugger.println("Retargeting failed: " + className + " from " + ih + " to " + firstIHInPatch); + if (IntegrationState.isActive()) { + throw IntegrationState.instrumentationFailed(className, e); + } System.exit(1); } return; diff --git a/src/main/java/com/lambda/Debugger/DebugifyingClassLoader.java b/src/main/java/com/lambda/Debugger/DebugifyingClassLoader.java index 16fbd37..e18dbaa 100644 --- a/src/main/java/com/lambda/Debugger/DebugifyingClassLoader.java +++ b/src/main/java/com/lambda/Debugger/DebugifyingClassLoader.java @@ -113,17 +113,29 @@ protected Class loadClass(String className, boolean resolve) try { clazz = findClass(className, instrument); } catch (VerifyError ve) { + if (IntegrationState.isActive()) + throw IntegrationState.instrumentationFailed(className, ve); println(spacesMinus() + "The ODB cannot instrument: " + className + ". Please report bug.\n" + ve); if (Debugger.TRACE_LOADER) ve.printStackTrace(); clazz = getParent().loadClass(className); } catch (IllegalStateException ise) { + if (IntegrationState.isActive()) + throw IntegrationState.instrumentationFailed(className, ise); println(spacesMinus() + "The ODB cannot instrument: " + className + ". Please report bug.\n" + ise); if (Debugger.TRACE_LOADER) ise.printStackTrace(); clazz = getParent().loadClass(className); + } catch (RuntimeException re) { + if (IntegrationState.isActive()) + throw IntegrationState.instrumentationFailed(className, re); + throw re; + } catch (LinkageError le) { + if (IntegrationState.isActive()) + throw IntegrationState.instrumentationFailed(className, le); + throw le; } if (clazz == null) diff --git a/src/main/java/com/lambda/Debugger/Defaults.java b/src/main/java/com/lambda/Debugger/Defaults.java index 8f85538..3d690f8 100644 --- a/src/main/java/com/lambda/Debugger/Defaults.java +++ b/src/main/java/com/lambda/Debugger/Defaults.java @@ -22,9 +22,8 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStreamWriter; @@ -67,9 +66,11 @@ public class Defaults { public static boolean readDefaults() { if (alreadyRead) return firstRun; alreadyRead = true; + String resolvedDefaultsFile = IntegrationState.defaultsFile(defaultsFile); try { if (!"com.lambda".startsWith("com")) SpecialFormatters.add("lambda.Debugger.SpecialTimeStampFormatter"); - BufferedReader br = new BufferedReader(new FileReader(defaultsFile)); + BufferedReader br = new BufferedReader(new InputStreamReader( + IntegrationState.defaultsInput(resolvedDefaultsFile))); Debugger.println("Reading .debuggerDefaults file..."); String line; while ((line = br.readLine()) != null) { @@ -124,6 +125,10 @@ public static boolean readDefaults() { return true; } catch (Exception e) { + if (IntegrationState.isActive()) { + throw IntegrationState.fatal("DEFAULTS_IO", "Could not read ODB defaults.", + e.getClass().getName(), e.getMessage(), 1); + } Debugger.println("Problem loading defaults file: "+e + ". Aborting load."); D.println(""); return false; @@ -316,8 +321,10 @@ static private String[] getStrings(String value) { } static public void writeDefaults(){ + String resolvedDefaultsFile = IntegrationState.defaultsFile(defaultsFile); try { - BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(defaultsFile))); + BufferedWriter w = new BufferedWriter(new OutputStreamWriter( + IntegrationState.defaultsOutput(resolvedDefaultsFile))); w.write("# ODB Defaults "+ Debugger.version +" -- You may edit by hand. See Manual for details\n\n"); w.write("# Class & method names must be complete. '*' must be freestanding.\n"); w.write("# DidntInstrument: This is informative only. (You may change to InstrumentOnly.)\n"); @@ -364,6 +371,10 @@ static public void writeDefaults(){ w.close(); } catch (IOException e) { + if (IntegrationState.isActive()) { + throw IntegrationState.fatal("DEFAULTS_IO", "Could not write ODB defaults.", + e.getClass().getName(), e.getMessage(), 1); + } Debugger.message("Could not save file " + defaultsFile, true); return; } diff --git a/src/main/java/com/lambda/Debugger/HashMapEq.java b/src/main/java/com/lambda/Debugger/HashMapEq.java index 432899e..d95c894 100644 --- a/src/main/java/com/lambda/Debugger/HashMapEq.java +++ b/src/main/java/com/lambda/Debugger/HashMapEq.java @@ -1,851 +1,125 @@ -/* HashMapEq.java - - Copyright 2003, Bil Lewis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* - * @(#)HashMap.java 1.29 99/04/22 + /* + * Copyright 2003, Bil Lewis * - * Copyright 1997-1999 by Sun Microsystems, Inc., - * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. - * All rights reserved. - * - * This software is the confidential and proprietary information - * of Sun Microsystems, Inc. ("Confidential Information"). You - * shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement - * you entered into with Sun. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any later + * version. */ - package com.lambda.Debugger; -import java.util.*; -import java.io.*; - -/** - * Hash table based implementation of the Map interface. This - * implementation provides all of the optional map operations, and permits - * null values and the null key. (The HashMap - * class is roughly equivalent to Hashtable, except that it is - * unsynchronized and permits nulls.) This class makes no guarantees as to - * the order of the map; in particular, it does not guarantee that the order - * will remain constant over time.

- * - * This implementation provides constant-time performance for the basic - * operations (get and put), assuming the hash function - * disperses the elements properly among the buckets. Iteration over - * collection views requires time proportional to the "capacity" of the - * HashMap instance (the number of buckets) plus its size (the number - * of key-value mappings). Thus, it's very important not to set the intial - * capacity too high (or the load factor too low) if iteration performance is - * important.

- * - * An instance of HashMap has two parameters that affect its - * performance: initial capacity and load factor. The - * capacity is the number of buckets in the hash table, and the initial - * capacity is simply the capacity at the time the hash table is created. The - * load factor is a measure of how full the hash table is allowed to - * get before its capacity is automatically increased. When the number of - * entries in the hash table exceeds the product of the load factor and the - * current capacity, the capacity is roughly doubled by calling the - * rehash method.

- * - * As a general rule, te default load factor (.75) offers a good tradeoff - * between time and space costs. Higher values decrease the space overhead - * but increase the lookup cost (reflected in most of the operations of the - * HashMap class, including get and put). The - * expected number of entries in the map and its load factor should be taken - * into account when setting its initial capacity, so as to minimize the - * number of rehash operations. If the initial capacity is greater - * than the maximum number of entries divided by the load factor, no - * rehash operations will ever occur.

- * - * If many mappings are to be stored in a HashMap instance, creating - * it with a sufficiently large capacity will allow the mappings to be stored - * more efficiently than letting it perform automatic rehashing as needed to - * grow the table.

- * - * Note that this implementation is not synchronized. If multiple - * threads access this map concurrently, and at least one of the threads - * modifies the map structurally, it must be synchronized externally. - * (A structural modification is any operation that adds or deletes one or - * more mappings; merely changing the value associated with a key that an - * instance already contains is not a structural modification.) This is - * typically accomplished by synchronizing on some object that naturally - * encapsulates the map. If no such object exists, the map should be - * "wrapped" using the Collections.synchronizedMap method. This is - * best done at creation time, to prevent accidental unsynchronized access to - * the map:

 Map m = Collections.synchronizedMap(new HashMap(...));
- * 

- * - * The iterators returned by all of this class's "collection view methods" are - * fail-fast: if the map is structurally modified at any time after the - * iterator is created, in any way except through the iterator's own - * remove or add methods, the iterator will throw a - * ConcurrentModificationException. Thus, in the face of concurrent - * modification, the iterator fails quickly and cleanly, rather than risking - * arbitrary, non-deterministic behavior at an undetermined time in the - * future. - * - * @author Josh Bloch - * @author Arthur van Hoff - * @version 1.29, 04/22/99 - * @see Object#hashCode() - * @see Collection - * @see Map - * @see TreeMap - * @see Hashtable - * @since JDK1.2 - */ +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +/** ODB's identity-based map compatibility type. */ public final class HashMapEq extends AbstractMap implements Map, Cloneable, - java.io.Serializable { - /** - * The hash table data. - */ - private transient Entry table[]; - - /** - * The total number of mappings in the hash table. - */ - private transient int count; - - /** - * The table is rehashed when its size exceeds this threshold. (The - * value of this field is (int)(capacity * loadFactor).) - * - * @serial - */ - private int threshold; - - /** - * The load factor for the hashtable. - * - * @serial - */ + Serializable { + private static final long serialVersionUID = 362498820763181265L; + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("loadFactor", Float.TYPE), + new ObjectStreamField("threshold", Integer.TYPE) + }; + private static final int DEFAULT_CAPACITY = 101; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + private IdentityHashMap delegate; + private int initialCapacity; private float loadFactor; - /** - * The number of times this HashMapEq has been structurally modified - * Structural modifications are those that change the number of mappings in - * the HashMapEq or otherwise modify its internal structure (e.g., - * rehash). This field is used to make iterators on Collection-views of - * the HashMapEq fail-fast. (See ConcurrentModificationException). - */ - private transient int modCount = 0; - - /** - * Constructs a new, empty map with the specified initial - * capacity and the specified load factor. - * - * @param initialCapacity the initial capacity of the HashMapEq. - * @param loadFactor the load factor of the HashMapEq - * @throws IllegalArgumentException if the initial capacity is less - * than zero, or if the load factor is nonpositive. - */ public HashMapEq(int initialCapacity, float loadFactor) { - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal Initial Capacity: "+ - initialCapacity); - if (loadFactor <= 0) - throw new IllegalArgumentException("Illegal Load factor: "+ - loadFactor); - if (initialCapacity==0) - initialCapacity = 1; - this.loadFactor = loadFactor; - table = new Entry[initialCapacity]; - threshold = (int)(initialCapacity * loadFactor); + if (initialCapacity < 0) { + throw new IllegalArgumentException( + "Illegal Initial Capacity: " + initialCapacity); + } + if (loadFactor <= 0) { + throw new IllegalArgumentException( + "Illegal Load factor: " + loadFactor); + } + this.initialCapacity = initialCapacity == 0 ? 1 : initialCapacity; + this.loadFactor = loadFactor; + delegate = new IdentityHashMap(initialCapacity); } - /** - * Constructs a new, empty map with the specified initial capacity - * and default load factor, which is 0.75. - * - * @param initialCapacity the initial capacity of the HashMapEq. - * @throws IllegalArgumentException if the initial capacity is less - * than zero. - */ public HashMapEq(int initialCapacity) { - this(initialCapacity, 0.75f); + this(initialCapacity, DEFAULT_LOAD_FACTOR); } - /** - * Constructs a new, empty map with a default capacity and load - * factor, which is 0.75. - */ public HashMapEq() { - this(101, 0.75f); - } - - /** - * Constructs a new map with the same mappings as the given map. The - * map is created with a capacity of twice the number of mappings in - * the given map or 11 (whichever is greater), and a default load factor, - * which is 0.75. - * @param t incoming map - */ - public HashMapEq(Map t) { - this(Math.max(2*t.size(), 11), 0.75f); - putAll(t); - } - - /** - * Returns the number of key-value mappings in this map. - * - * @return the number of key-value mappings in this map. - */ - public int size() { - return count; + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); } - /** - * Returns true if this map contains no key-value mappings. - * - * @return true if this map contains no key-value mappings. - */ - public boolean isEmpty() { - return count == 0; + public HashMapEq(Map source) { + this(Math.max(2 * source.size(), 11), DEFAULT_LOAD_FACTOR); + putAll(source); } - /** - * Returns true if this map maps one or more keys to the - * specified value. - * - * @param value value whose presence in this map is to be tested. - * @return true if this map maps one or more keys to the - * specified value. - */ - public boolean containsValue(Object value) { - Entry tab[] = table; - - if (value==null) { - for (int i = tab.length ; i-- > 0 ;) - for (Entry e = tab[i] ; e != null ; e = e.next) - if (e.value==null) - return true; - } else { - for (int i = tab.length ; i-- > 0 ;) - for (Entry e = tab[i] ; e != null ; e = e.next) - if (value == e.value) // BIL'S MOD - return true; - } - - return false; - } - - /** - * Returns true if this map contains a mapping for the specified - * key. - * - * @return true if this map contains a mapping for the specified - * key. - * @param key key whose presence in this Map is to be tested. - */ - public boolean containsKey(Object key) { - Entry tab[] = table; - if (key != null) { - int hash = System.identityHashCode(key); - int index = (hash & 0x7FFFFFFF) % tab.length; - for (Entry e = tab[index]; e != null; e = e.next) - if (e.hash==hash && key == e.key) // BIL'S MOD - return true; - } else { - for (Entry e = tab[0]; e != null; e = e.next) - if (e.key==null) - return true; - } - - return false; - } - - /** - * Returns the value to which this map maps the specified key. Returns - * null if the map contains no mapping for this key. A return - * value of null does not necessarily indicate that the - * map contains no mapping for the key; it's also possible that the map - * explicitly maps the key to null. The containsKey - * operation may be used to distinguish these two cases. - * - * @return the value to which this map maps the specified key. - * @param key key whose associated value is to be returned. - */ - public Object get(Object key) { - Entry tab[] = table; - - if (key != null) { - int hash = System.identityHashCode(key); - int index = (hash & 0x7FFFFFFF) % tab.length; - for (Entry e = tab[index]; e != null; e = e.next) - if ((e.hash == hash) && key == e.key) // BIL'S MOD - return e.value; - } else { - for (Entry e = tab[0]; e != null; e = e.next) - if (e.key==null) - return e.value; - } - - return null; - } - - /** - * Rehashes the contents of this map into a new HashMapEq instance - * with a larger capacity. This method is called automatically when the - * number of keys in this map exceeds its capacity and load factor. - */ - private void rehash() { - int oldCapacity = table.length; - Entry oldMap[] = table; - - int newCapacity = oldCapacity * 2 + 1; - Entry newMap[] = new Entry[newCapacity]; - - modCount++; - threshold = (int)(newCapacity * loadFactor); - table = newMap; - - for (int i = oldCapacity ; i-- > 0 ;) { - for (Entry old = oldMap[i] ; old != null ; ) { - Entry e = old; - old = old.next; - - int index = (e.hash & 0x7FFFFFFF) % newCapacity; - e.next = newMap[index]; - newMap[index] = e; - } - } - } - - /** - * Associates the specified value with the specified key in this map. - * If the map previously contained a mapping for this key, the old - * value is replaced. - * - * @param key key with which the specified value is to be associated. - * @param value value to be associated with the specified key. - * @return previous value associated with specified key, or null - * if there was no mapping for key. A null return can - * also indicate that the HashMapEq previously associated - * null with the specified key. - */ + public int size() { return delegate.size(); } + public boolean isEmpty() { return delegate.isEmpty(); } + public boolean containsValue(Object value) { return delegate.containsValue(value); } + public boolean containsKey(Object key) { return delegate.containsKey(key); } + public Object get(Object key) { return delegate.get(key); } public Object put(Object key, Object value) { - // Makes sure the key is not already in the HashMapEq. - Entry tab[] = table; - int hash = 0; - int index = 0; - - if (key != null) { - hash = System.identityHashCode(key); - index = (hash & 0x7FFFFFFF) % tab.length; - for (Entry e = tab[index] ; e != null ; e = e.next) { - if ((e.hash == hash) && key == e.key) { // BIL'S MOD - Object old = e.value; - e.value = value; - return old; - } - } - } else { - for (Entry e = tab[0] ; e != null ; e = e.next) { - if (e.key == null) { - Object old = e.value; - e.value = value; - return old; - } - } + if (!delegate.containsKey(key) + && delegate.size() >= (int) (initialCapacity * loadFactor)) { + initialCapacity = 2 * initialCapacity + 1; } - - modCount++; - if (count >= threshold) { - // Rehash the table if the threshold is exceeded - rehash(); - - tab = table; - index = (hash & 0x7FFFFFFF) % tab.length; - } - - // Creates the new entry. - Entry e = new Entry(hash, key, value, tab[index]); - tab[index] = e; - count++; - return null; + return delegate.put(key, value); } - - /** - * Removes the mapping for this key from this map if present. - * - * @param key key whose mapping is to be removed from the map. - * @return previous value associated with specified key, or null - * if there was no mapping for key. A null return can - * also indicate that the map previously associated null - * with the specified key. - */ - public Object remove(Object key) { - Entry tab[] = table; - - if (key != null) { - int hash = System.identityHashCode(key); - int index = (hash & 0x7FFFFFFF) % tab.length; - - for (Entry e = tab[index], prev = null; e != null; - prev = e, e = e.next) { - if ((e.hash == hash) && key == e.key) { // BIL'S MOD - modCount++; - if (prev != null) - prev.next = e.next; - else - tab[index] = e.next; - - count--; - Object oldValue = e.value; - e.value = null; - return oldValue; - } - } - } else { - for (Entry e = tab[0], prev = null; e != null; - prev = e, e = e.next) { - if (e.key == null) { - modCount++; - if (prev != null) - prev.next = e.next; - else - tab[0] = e.next; - - count--; - Object oldValue = e.value; - e.value = null; - return oldValue; - } - } + public Object remove(Object key) { return delegate.remove(key); } + public void putAll(Map source) { + for (Object entryObject : source.entrySet()) { + Map.Entry entry = (Map.Entry) entryObject; + put(entry.getKey(), entry.getValue()); } - - return null; } + public void clear() { delegate.clear(); } - /** - * Copies all of the mappings from the specified map to this one. - * - * These mappings replace any mappings that this map had for any of the - * keys currently in the specified Map. - * - * @param t Mappings to be stored in this map. - */ - public void putAll(Map t) { - Iterator i = t.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry e = (Map.Entry) i.next(); - put(e.getKey(), e.getValue()); - } - } - - /** - * Removes all mappings from this map. - */ - public void clear() { - Entry tab[] = table; - modCount++; - for (int index = tab.length; --index >= 0; ) - tab[index] = null; - count = 0; - } - - /** - * Returns a shallow copy of this HashMapEq instance: the keys and - * values themselves are not cloned. - * - * @return a shallow copy of this map. - */ public Object clone() { - try { - HashMapEq t = (HashMapEq)super.clone(); - t.table = new Entry[table.length]; - for (int i = table.length ; i-- > 0 ; ) { - t.table[i] = (table[i] != null) - ? (Entry)table[i].clone() : null; - } - t.keySet = null; - t.entrySet = null; - t.values = null; - t.modCount = 0; - return t; - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - // Views - - private transient Set keySet = null; - private transient Set entrySet = null; - private transient Collection values = null; - - /** - * Returns a set view of the keys contained in this map. The set is - * backed by the map, so changes to the map are reflected in the set, and - * vice-versa. The set supports element removal, which removes the - * corresponding mapping from this map, via the Iterator.remove, - * Set.remove, removeAll, retainAll, and - * clear operations. It does not support the add or - * addAll operations. - * - * @return a set view of the keys contained in this map. - */ - public Set keySet() { - if (keySet == null) { - keySet = new AbstractSet() { - public Iterator iterator() { - return new HashIterator(KEYS); - } - public int size() { - return count; - } - public boolean contains(Object o) { - return containsKey(o); - } - public boolean remove(Object o) { - return HashMapEq.this.remove(o) != null; - } - public void clear() { - HashMapEq.this.clear(); - } - }; - } - return keySet; - } - - /** - * Returns a collection view of the values contained in this map. The - * collection is backed by the map, so changes to the map are reflected in - * the collection, and vice-versa. The collection supports element - * removal, which removes the corresponding mapping from this map, via the - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - * - * @return a collection view of the values contained in this map. - */ - public Collection values() { - if (values==null) { - values = new AbstractCollection() { - public Iterator iterator() { - return new HashIterator(VALUES); - } - public int size() { - return count; - } - public boolean contains(Object o) { - return containsValue(o); - } - public void clear() { - HashMapEq.this.clear(); - } - }; + HashMapEq copy = new HashMapEq(initialCapacity, loadFactor); + copy.delegate.putAll(delegate); + return copy; + } + + public Set keySet() { return delegate.keySet(); } + public Collection values() { return delegate.values(); } + public Set entrySet() { return delegate.entrySet(); } + + int capacity() { return initialCapacity; } + float loadFactor() { return loadFactor; } + + public synchronized String toString() { return delegate.toString(); } + + private void writeObject(ObjectOutputStream stream) throws IOException { + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("loadFactor", loadFactor); + fields.put("threshold", (int) (initialCapacity * loadFactor)); + stream.writeFields(); + stream.writeInt(initialCapacity); + stream.writeInt(delegate.size()); + for (Object entryObject : delegate.entrySet()) { + Map.Entry entry = (Map.Entry) entryObject; + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); } - return values; } - /** - * Returns a collection view of the mappings contained in this map. Each - * element in the returned collection is a Map.Entry. The - * collection is backed by the map, so changes to the map are reflected in - * the collection, and vice-versa. The collection supports element - * removal, which removes the corresponding mapping from the map, via the - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - * - * @return a collection view of the mappings contained in this map. - * @see Map.Entry - */ - public Set entrySet() { - if (entrySet==null) { - entrySet = new AbstractSet() { - public Iterator iterator() { - return new HashIterator(ENTRIES); - } - - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry entry = (Map.Entry)o; - Object key = entry.getKey(); - Entry tab[] = table; - int hash = (key==null ? 0 : System.identityHashCode(key)); - int index = (hash & 0x7FFFFFFF) % tab.length; - - for (Entry e = tab[index]; e != null; e = e.next) - if (e.hash==hash && e == entry) // BIL'S MOD - return true; - return false; - } - - public boolean remove(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry entry = (Map.Entry)o; - Object key = entry.getKey(); - Entry tab[] = table; - int hash = (key==null ? 0 : System.identityHashCode(key)); - int index = (hash & 0x7FFFFFFF) % tab.length; - - for (Entry e = tab[index], prev = null; e != null; - prev = e, e = e.next) { - if (e.hash==hash && e == entry) { // BIL'S MOD - modCount++; - if (prev != null) - prev.next = e.next; - else - tab[index] = e.next; - - count--; - e.value = null; - return true; - } - } - return false; - } - - public int size() { - return count; - } - - public void clear() { - HashMapEq.this.clear(); - } - }; + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + loadFactor = fields.get("loadFactor", DEFAULT_LOAD_FACTOR); + initialCapacity = stream.readInt(); + int size = stream.readInt(); + delegate = new IdentityHashMap(initialCapacity); + for (int i = 0; i < size; i++) { + put(stream.readObject(), stream.readObject()); } - - return entrySet; - } - - /** - * HashMapEq collision list entry. - */ - private static class Entry implements Map.Entry { - int hash; - Object key; - Object value; - Entry next; - - Entry(int hash, Object key, Object value, Entry next) { - this.hash = hash; - this.key = key; - this.value = value; - this.next = next; - } - - protected Object clone() { - return new Entry(hash, key, value, - (next==null ? null : (Entry)next.clone())); - } - - // Map.Entry Ops - - public Object getKey() { - return key; - } - - public Object getValue() { - return value; - } - - public Object setValue(Object value) { - Object oldValue = this.value; - this.value = value; - return oldValue; - } - - public boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - - return (key==null ? e.getKey()==null : key == e.getKey()) && // BIL'S MOD - (value==null ? e.getValue()==null : value == e.getValue()); // BIL'S MOD - } - - public int hashCode() { - return hash ^ (value==null ? 0 : value.hashCode()); - } - - public String toString() { - return key+"="+value; - } - } - - // Types of Iterators - private static final int KEYS = 0; - private static final int VALUES = 1; - private static final int ENTRIES = 2; - - private class HashIterator implements Iterator { - Entry[] table = HashMapEq.this.table; - int index = table.length; - Entry entry = null; - Entry lastReturned = null; - int type; - - /** - * The modCount value that the iterator believes that the backing - * List should have. If this expectation is violated, the iterator - * has detected concurrent modification. - */ - private int expectedModCount = modCount; - - HashIterator(int type) { - this.type = type; - } - - public boolean hasNext() { - while (entry==null && index>0) - entry = table[--index]; - - return entry != null; - } - - public Object next() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - - while (entry==null && index>0) - entry = table[--index]; - - if (entry != null) { - Entry e = lastReturned = entry; - entry = e.next; - return type == KEYS ? e.key : (type == VALUES ? e.value : e); - } - throw new NoSuchElementException(); - } - - public void remove() { - if (lastReturned == null) - throw new IllegalStateException(); - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - - Entry[] tab = HashMapEq.this.table; - int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length; - - for (Entry e = tab[index], prev = null; e != null; - prev = e, e = e.next) { - if (e == lastReturned) { - modCount++; - expectedModCount++; - if (prev == null) - tab[index] = e.next; - else - prev.next = e.next; - count--; - lastReturned = null; - return; - } - } - throw new ConcurrentModificationException(); - } - } - - /** - * Save the state of the HashMapEq instance to a stream (i.e., - * serialize it). - * - * @serialData The capacity of the HashMapEq (the length of the - * bucket array) is emitted (int), followed by the - * size of the HashMapEq (the number of key-value - * mappings), followed by the key (Object) and value (Object) - * for each key-value mapping represented by the HashMapEq - * The key-value mappings are emitted in no particular order. - */ - private void writeObject(java.io.ObjectOutputStream s) - throws IOException - { - // Write out the threshold, loadfactor, and any hidden stuff - s.defaultWriteObject(); - - // Write out number of buckets - s.writeInt(table.length); - - // Write out size (number of Mappings) - s.writeInt(count); - - // Write out keys and values (alternating) - for (int index = table.length-1; index >= 0; index--) { - Entry entry = table[index]; - - while (entry != null) { - s.writeObject(entry.key); - s.writeObject(entry.value); - entry = entry.next; - } - } - } - - private static final long serialVersionUID = 362498820763181265L; - - /** - * Reconstitute the HashMapEq instance from a stream (i.e., - * deserialize it). - */ - private void readObject(java.io.ObjectInputStream s) - throws IOException, ClassNotFoundException - { - // Read in the threshold, loadfactor, and any hidden stuff - s.defaultReadObject(); - - // Read in number of buckets and allocate the bucket array; - int numBuckets = s.readInt(); - table = new Entry[numBuckets]; - - // Read in size (number of Mappings) - int size = s.readInt(); - - // Read the keys and values, and put the mappings in the HashMapEq - for (int i=0; i= 2) { + recordingStarted = true; + emit("recording-started", "\"created\":" + created + ",\"retained\":" + retained); + } + } + + static void debuggerReady(long created, long retained) { + requireUsefulRecording(created, retained); + if (active != null) { + active.emitDebuggerReady(created, retained); + } + } + + private synchronized void emitDebuggerReady(long created, long retained) { + recordingStarted(created, retained); + if (!debuggerReady) { + debuggerReady = true; + emit("debugger-ready", "\"created\":" + created + ",\"retained\":" + retained); + } + } + + static Error badContract(PrintStream error, String token, String message) { + PrintStream destination = error == null ? System.err : error; + String safeToken = token != null && token.matches("[0-9a-f]{32}") ? token : ""; + destination.println(PREFIX + safeToken + "\t{\"version\":1,\"sequence\":1,\"type\":\"fatal\"," + + "\"code\":\"BAD_CONTRACT\",\"message\":" + quote(message) + "}"); + destination.flush(); + Runtime.getRuntime().halt(2); + return new AssertionError(message); + } + + static Error fatal(String code, String message, String errorClass, String cause, int exitCode) { + IntegrationState state = active; + if (state == null) { + return new AssertionError(message); + } + StringBuilder fields = new StringBuilder(); + fields.append("\"code\":").append(quote(code)); + fields.append(",\"message\":").append(quote(message)); + if (errorClass != null) { + fields.append(",\"class\":").append(quote(errorClass)); + } + if (cause != null) { + fields.append(",\"cause\":").append(quote(cause)); + } + state.emit("fatal", fields.toString()); + Runtime.getRuntime().halt(exitCode); + return new AssertionError(message); + } + + private synchronized void emit(String type, String additionalFields) { + sequence++; + originalError.println(PREFIX + token + "\t{\"version\":1,\"sequence\":" + sequence + + ",\"type\":" + quote(type) + "," + additionalFields + "}"); + originalError.flush(); + } + + private static String quote(String value) { + StringBuilder escaped = new StringBuilder("\""); + for (int index = 0; index < value.length(); index++) { + char character = value.charAt(index); + switch (character) { + case '\"': + escaped.append("\\\""); + break; + case '\\': + escaped.append("\\\\"); + break; + case '\b': + escaped.append("\\b"); + break; + case '\f': + escaped.append("\\f"); + break; + case '\n': + escaped.append("\\n"); + break; + case '\r': + escaped.append("\\r"); + break; + case '\t': + escaped.append("\\t"); + break; + default: + if (character < 0x20 || character > 0x7e) { + escaped.append(String.format("\\u%04x", (int) character)); + } else { + escaped.append(character); + } + } + } + return escaped.append('\"').toString(); + } + + static final class Launch { + final String target; + final String[] arguments; + + Launch(String target, String[] arguments) { + this.target = target; + this.arguments = arguments; + } + } + + static final class BadContract extends Exception { + final String token; + + BadContract(String token, String message) { + super(message); + this.token = token; + } + } +} diff --git a/src/main/java/com/lambda/Debugger/MyAbstractCollection.java b/src/main/java/com/lambda/Debugger/MyAbstractCollection.java index 4103199..1297368 100644 --- a/src/main/java/com/lambda/Debugger/MyAbstractCollection.java +++ b/src/main/java/com/lambda/Debugger/MyAbstractCollection.java @@ -1,482 +1,19 @@ -/* MyAbstractCollection.java - - Copyright 2003, Bil Lewis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* - * @(#)MyAbstractCollection.java 1.16 00/02/02 + /* + * Copyright 2003, Bil Lewis * - * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. - * - * This software is the proprietary information of Sun Microsystems, Inc. - * Use is subject to license terms. - * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any later + * version. */ - -//package java.util; package com.lambda.Debugger; -import java.util.*; +import java.util.AbstractCollection; +import java.util.Collection; -/** - * This class provides a skeletal implementation of the Collection - * interface, to minimize the effort required to implement this interface.

- * - * To implement an unmodifiable collection, the programmer needs only to - * extend this class and provide implementations for the iterator and - * size methods. (The iterator returned by the iterator - * method must implement hasNext and next.)

- * - * To implement a modifiable collection, the programmer must additionally - * override this class's add method (which otherwise throws an - * UnsupportedOperationException), and the iterator returned by the - * iterator method must additionally implement its remove - * method.

- * - * The programmer should generally provide a void (no argument) and - * Collection constructor, as per the recommendation in the - * Collection interface specification.

- * - * The documentation for each non-abstract methods in this class describes its - * implementation in detail. Each of these methods may be overridden if - * the collection being implemented admits a more efficient implementation. - * - * @author Josh Bloch - * @version 1.16, 02/02/00 - * @see Collection - * @since 1.2 - */ - -public abstract class MyAbstractCollection implements Collection { - /** - * Sole constructor. (For invocation by subclass constructors, typically - * implicit.) - */ +/** Retained ODB compatibility name for Java's collection skeleton. */ +public abstract class MyAbstractCollection extends AbstractCollection + implements Collection { protected MyAbstractCollection() { } - - // Query Operations - - /** - * Returns an iterator over the elements contained in this collection. - * - * @return an iterator over the elements contained in this collection. - */ - public abstract Iterator iterator(); - - /** - * Returns the number of elements in this collection. If the collection - * contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. - * - * @return the number of elements in this collection. - */ - public abstract int size(); - - /** - * Returns true if this collection contains no elements.

- * - * This implementation returns size() == 0. - * - * @return true if this collection contains no elements. - */ - public boolean isEmpty() { - return size() == 0; - } - - /** - * Returns true if this collection contains the specified - * element. More formally, returns true if and only if this - * collection contains at least one element e such that - * (o==null ? e==null : o.equals(e)).

- * - * This implementation iterates over the elements in the collection, - * checking each element in turn for equality with the specified element. - * - * @param o object to be checked for containment in this collection. - * @return true if this collection contains the specified element. - */ - public boolean contains(Object o) { - Iterator e = iterator(); - if (o==null) { - while (e.hasNext()) - if (e.next()==null) - return true; - } else { - while (e.hasNext()) - if (o.equals(e.next())) - return true; - } - return false; - } - - /** - * Returns an array containing all of the elements in this collection. If - * the collection makes any guarantees as to what order its elements are - * returned by its iterator, this method must return the elements in the - * same order. The returned array will be "safe" in that no references to - * it are maintained by the collection. (In other words, this method must - * allocate a new array even if the collection is backed by an Array). - * The caller is thus free to modify the returned array.

- * - * This implementation allocates the array to be returned, and iterates - * over the elements in the collection, storing each object reference in - * the next consecutive element of the array, starting with element 0. - * - * @return an array containing all of the elements in this collection. - */ - public Object[] toArray() { - Object[] result = new Object[size()]; - Iterator e = iterator(); - for (int i=0; e.hasNext(); i++) - result[i] = e.next(); - return result; - } - - /** - * Returns an array with a runtime type is that of the specified array and - * that contains all of the elements in this collection. If the - * collection fits in the specified array, it is returned therein. - * Otherwise, a new array is allocated with the runtime type of the - * specified array and the size of this collection.

- * - * If the collection fits in the specified array with room to spare (i.e., - * the array has more elements than the collection), the element in the - * array immediately following the end of the collection is set to - * null. This is useful in determining the length of the - * collection only if the caller knows that the collection does - * not contain any null elements.)

- * - * If this collection makes any guarantees as to what order its elements - * are returned by its iterator, this method must return the elements in - * the same order.

- * - * This implementation checks if the array is large enough to contain the - * collection; if not, it allocates a new array of the correct size and - * type (using reflection). Then, it iterates over the collection, - * storing each object reference in the next consecutive element of the - * array, starting with element 0. If the array is larger than the - * collection, a null is stored in the first location after the - * end of the collection. - * - * @param a the array into which the elements of the collection are to - * be stored, if it is big enough; otherwise, a new array of the - * same runtime type is allocated for this purpose. - * @return an array containing the elements of the collection. - * - * @throws NullPointerException if the specified array is null. - * - * @throws ArrayStoreException if the runtime type of the specified array - * is not a supertype of the runtime type of every element in this - * collection. - */ - public Object[] toArray(Object a[]) { - int size = size(); - if (a.length < size) - a = (Object[])java.lang.reflect.Array.newInstance( - a.getClass().getComponentType(), size); - - Iterator it=iterator(); - for (int i=0; i size) - a[size] = null; - - return a; - } - - // Modification Operations - - /** - * Ensures that this collection contains the specified element (optional - * operation). Returns true if the collection changed as a - * result of the call. (Returns false if this collection does - * not permit duplicates and already contains the specified element.) - * Collections that support this operation may place limitations on what - * elements may be added to the collection. In particular, some - * collections will refuse to add null elements, and others will - * impose restrictions on the type of elements that may be added. - * Collection classes should clearly specify in their documentation any - * restrictions on what elements may be added.

- * - * This implementation always throws an - * UnsupportedOperationException. - * - * @param o element whose presence in this collection is to be ensured. - * @return true if the collection changed as a result of the call. - * - * @throws UnsupportedOperationException if the add method is not - * supported by this collection. - * - * @throws NullPointerException if this collection does not permit - * null elements, and the specified element is - * null. - * - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this collection. - * - * @throws IllegalArgumentException if some aspect of this element - * prevents it from being added to this collection. - */ - public boolean add(Object o) { - throw new UnsupportedOperationException(); - } - - /** - * Removes a single instance of the specified element from this - * collection, if it is present (optional operation). More formally, - * removes an element e such that (o==null ? e==null : - * o.equals(e)), if the collection contains one or more such - * elements. Returns true if the collection contained the - * specified element (or equivalently, if the collection changed as a - * result of the call).

- * - * This implementation iterates over the collection looking for the - * specified element. If it finds the element, it removes the element - * from the collection using the iterator's remove method.

- * - * Note that this implementation throws an - * UnsupportedOperationException if the iterator returned by this - * collection's iterator method does not implement the remove - * method. - * - * @param o element to be removed from this collection, if present. - * @return true if the collection contained the specified - * element. - * - * @throws UnsupportedOperationException if the remove method is - * not supported by this collection. - */ - public boolean remove(Object o) { - Iterator e = iterator(); - if (o==null) { - while (e.hasNext()) { - if (e.next()==null) { - e.remove(); - return true; - } - } - } else { - while (e.hasNext()) { - if (o.equals(e.next())) { - e.remove(); - return true; - } - } - } - return false; - } - - - // Bulk Operations - - /** - * Returns true if this collection contains all of the elements - * in the specified collection.

- * - * This implementation iterates over the specified collection, checking - * each element returned by the iterator in turn to see if it's - * contained in this collection. If all elements are so contained - * true is returned, otherwise false. - * - * @param c collection to be checked for containment in this collection. - * @return true if this collection contains all of the elements - * in the specified collection. - * - * @see #contains(Object) - */ - public boolean containsAll(Collection c) { - Iterator e = c.iterator(); - while (e.hasNext()) - if(!contains(e.next())) - return false; - - return true; - } - - /** - * Adds all of the elements in the specified collection to this collection - * (optional operation). The behavior of this operation is undefined if - * the specified collection is modified while the operation is in - * progress. (This implies that the behavior of this call is undefined if - * the specified collection is this collection, and this collection is - * nonempty.)

- * - * This implementation iterates over the specified collection, and adds - * each object returned by the iterator to this collection, in turn.

- * - * Note that this implementation will throw an - * UnsupportedOperationException unless add is - * overridden. - * - * @param c collection whose elements are to be added to this collection. - * @return true if this collection changed as a result of the - * call. - * @throws UnsupportedOperationException if the addAll method is - * not supported by this collection. - * - * @see #add(Object) - */ - public boolean addAll(Collection c) { - boolean modified = false; - Iterator e = c.iterator(); - while (e.hasNext()) { - if(add(e.next())) - modified = true; - } - return modified; - } - - /** - * Removes from this collection all of its elements that are contained in - * the specified collection (optional operation).

- * - * This implementation iterates over this collection, checking each - * element returned by the iterator in turn to see if it's contained - * in the specified collection. If it's so contained, it's removed from - * this collection with the iterator's remove method.

- * - * Note that this implementation will throw an - * UnsupportedOperationException if the iterator returned by the - * iterator method does not implement the remove method. - * - * @param c elements to be removed from this collection. - * @return true if this collection changed as a result of the - * call. - * - * @throws UnsupportedOperationException removeAll is not supported - * by this collection. - * - * @see #remove(Object) - * @see #contains(Object) - */ - public boolean removeAll(Collection c) { - boolean modified = false; - Iterator e = iterator(); - while (e.hasNext()) { - if(c.contains(e.next())) { - e.remove(); - modified = true; - } - } - return modified; - } - - /** - * Retains only the elements in this collection that are contained in the - * specified collection (optional operation). In other words, removes - * from this collection all of its elements that are not contained in the - * specified collection.

- * - * This implementation iterates over this collection, checking each - * element returned by the iterator in turn to see if it's contained - * in the specified collection. If it's not so contained, it's removed - * from this collection with the iterator's remove method.

- * - * Note that this implementation will throw an - * UnsupportedOperationException if the iterator returned by the - * iterator method does not implement the remove method. - * - * @param c elements to be retained in this collection. - * @return true if this collection changed as a result of the - * call. - * - * @throws UnsupportedOperationException if the retainAll method - * is not supported by this collection. - * - * @see #remove(Object) - * @see #contains(Object) - */ - public boolean retainAll(Collection c) { - boolean modified = false; - Iterator e = iterator(); - while (e.hasNext()) { - if(!c.contains(e.next())) { - e.remove(); - modified = true; - } - } - return modified; - } - - /** - * Removes all of the elements from this collection (optional operation). - * The collection will be empty after this call returns (unless it throws - * an exception).

- * - * This implementation iterates over this collection, removing each - * element using the Iterator.remove operation. Most - * implementations will probably choose to override this method for - * efficiency.

- * - * Note that this implementation will throw an - * UnsupportedOperationException if the iterator returned by this - * collection's iterator method does not implement the - * remove method. - * - * @throws UnsupportedOperationException if the remove method is - * not supported by this collection. - */ - public void clear() { - Iterator e = iterator(); - while (e.hasNext()) { - e.next(); - e.remove(); - } - } - - - // String conversion - - /** - * Returns a string representation of this collection. The string - * representation consists of a list of the collection's elements in the - * order they are returned by its iterator, enclosed in square brackets - * ("[]"). Adjacent elements are separated by the characters - * ", " (comma and space). Elements are converted to strings as - * by String.valueOf(Object).

- * - * This implementation creates an empty string buffer, appends a left - * square bracket, and iterates over the collection appending the string - * representation of each element in turn. After appending each element - * except the last, the string ", " is appended. Finally a right - * bracket is appended. A string is obtained from the string buffer, and - * returned. - * - * @return a string representation of this collection. - */ - /* - public String toString() { - StringBuffer buf = new StringBuffer(); - Iterator e = iterator(); - buf.append("["); - int maxIndex = size() - 1; - for (int i = 0; i <= maxIndex; i++) { - buf.append(String.valueOf(e.next())); - if (i < maxIndex) - buf.append(", "); - } - buf.append("]"); - return buf.toString(); - } - */ - - public String toString() { - return ""; - } } diff --git a/src/main/java/com/lambda/Debugger/MyAbstractList.java b/src/main/java/com/lambda/Debugger/MyAbstractList.java index c5aa194..0b589b1 100644 --- a/src/main/java/com/lambda/Debugger/MyAbstractList.java +++ b/src/main/java/com/lambda/Debugger/MyAbstractList.java @@ -1,870 +1,270 @@ -/* MyAbstractList.java - - Copyright 2003, Bil Lewis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* - * @(#)MyAbstractList.java 1.31 00/02/02 + /* + * Copyright 2003, Bil Lewis * - * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. - * - * This software is the proprietary information of Sun Microsystems, Inc. - * Use is subject to license terms. - * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any later + * version. */ - -//package java.util; package com.lambda.Debugger; -import java.util.*; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; -/** - * This class provides a skeletal implementation of the List - * interface to minimize the effort required to implement this interface - * backed by a "random access" data store (such as an array). For sequential - * access data (such as a linked list), AbstractSequentialList should - * be used in preference to this class.

- * - * To implement an unmodifiable list, the programmer needs only to extend this - * class and provide implementations for the get(int index) and - * size() methods.

- * - * To implement a modifiable list, the programmer must additionally override - * the set(int index, Object element) method (which otherwise throws - * an UnsupportedOperationException. If the list is variable-size - * the programmer must additionally override the add(int index, Object - * element) and remove(int index) methods.

- * - * The programmer should generally provide a void (no argument) and collection - * constructor, as per the recommendation in the Collection interface - * specification.

- * - * Unlike the other abstract collection implementations, the programmer does - * not have to provide an iterator implementation; the iterator and - * list iterator are implemented by this class, on top the "random access" - * methods: get(int index), set(int index, Object element), - * set(int index, Object element), add(int index, Object - * element) and remove(int index).

- * - * The documentation for each non-abstract methods in this class describes its - * implementation in detail. Each of these methods may be overridden if the - * collection being implemented admits a more efficient implementation. - * - * @author Josh Bloch - * @version 1.31, 02/02/00 - * @see Collection - * @see List - * @see AbstractSequentialList - * @see AbstractCollection - * @since 1.2 - */ +/** Retained ODB compatibility name for an indexed list skeleton. */ +public abstract class MyAbstractList extends MyAbstractCollection + implements List { + protected transient int modCount = 0; -public abstract class MyAbstractList extends MyAbstractCollection implements List { - /** - * Sole constructor. (For invocation by subclass constructors, typically - * implicit.) - */ protected MyAbstractList() { } - /** - * Appends the specified element to the end of this List (optional - * operation).

- * - * This implementation calls add(size(), o).

- * - * Note that this implementation throws an - * UnsupportedOperationException unless add(int, Object) - * is overridden. - * - * @param o element to be appended to this list. - * - * @return true (as per the general contract of - * Collection.add). - * - * @throws UnsupportedOperationException if the add method is not - * supported by this Set. - * - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this set. - * - * @throws IllegalArgumentException some aspect of this element prevents - * it from being added to this collection. - */ - public boolean add(Object o) { - add(size(), o); - return true; + public abstract Object get(int index); + + public boolean add(Object value) { + add(size(), value); + return true; } - /** - * Returns the element at the specified position in this list. - * - * @param index index of element to return. - * - * @return the element at the specified position in this list. - * @throws IndexOutOfBoundsException if the given index is out of range - * (index < 0 || index >= size()). - */ - abstract public Object get(int index); - - /** - * Replaces the element at the specified position in this list with the - * specified element (optional operation).

- * - * This implementation always throws an - * UnsupportedOperationException. - * - * @param index index of element to replace. - * @param element element to be stored at the specified position. - * @return the element previously at the specified position. - * - * @throws UnsupportedOperationException if the set method is not - * supported by this List. - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this list. - * @throws IllegalArgumentException if some aspect of the specified - * element prevents it from being added to this list. - * - * @throws IndexOutOfBoundsException if the specified index is out of - * range (index < 0 || index >= size()). - */ - - public Object set(int index, Object element) { - throw new UnsupportedOperationException(); + public Object set(int index, Object value) { + throw new UnsupportedOperationException(); } - /** - * Inserts the specified element at the specified position in this list - * (optional operation). Shifts the element currently at that position - * (if any) and any subsequent elements to the right (adds one to their - * indices).

- * - * This implementation always throws an UnsupportedOperationException. - * - * @param index index at which the specified element is to be inserted. - * @param element element to be inserted. - * - * @throws UnsupportedOperationException if the add method is not - * supported by this list. - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this list. - * @throws IllegalArgumentException if some aspect of the specified - * element prevents it from being added to this list. - * @throws IndexOutOfBoundsException index is out of range (index < - * 0 || index > size()). - */ - public void add(int index, Object element) { - throw new UnsupportedOperationException(); + public void add(int index, Object value) { + throw new UnsupportedOperationException(); } - /** - * Removes the element at the specified position in this list (optional - * operation). Shifts any subsequent elements to the left (subtracts one - * from their indices). Returns the element that was removed from the - * list.

- * - * This implementation always throws an - * UnsupportedOperationException. - * - * @param index the index of the element to remove. - * @return the element previously at the specified position. - * - * @throws UnsupportedOperationException if the remove method is - * not supported by this list. - * @throws IndexOutOfBoundsException if the specified index is out of - * range (index < 0 || index >= size()). - */ public Object remove(int index) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException(); } - - // Search Operations - - /** - * Returns the index in this list of the first occurence of the specified - * element, or -1 if the list does not contain this element. More - * formally, returns the lowest index i such that (o==null ? - * get(i)==null : o.equals(get(i))), or -1 if there is no such - * index.

- * - * This implementation first gets a list iterator (with - * listIterator()). Then, it iterates over the list until the - * specified element is found or the end of the list is reached. - * - * @param o element to search for. - * - * @return the index in this List of the first occurence of the specified - * element, or -1 if the List does not contain this element. - */ - public int indexOf(Object o) { - ListIterator e = listIterator(); - if (o==null) { - while (e.hasNext()) - if (e.next()==null) - return e.previousIndex(); - } else { - while (e.hasNext()) - if (o.equals(e.next())) - return e.previousIndex(); - } - return -1; + public int indexOf(Object value) { + ListIterator iterator = listIterator(); + while (iterator.hasNext()) { + Object candidate = iterator.next(); + if (value == null ? candidate == null : value.equals(candidate)) { + return iterator.previousIndex(); + } + } + return -1; } - /** - * Returns the index in this list of the last occurence of the specified - * element, or -1 if the list does not contain this element. More - * formally, returns the highest index i such that (o==null ? - * get(i)==null : o.equals(get(i))), or -1 if there is no such - * index.

- * - * This implementation first gets a list iterator that points to the end - * of the list (with listIterator(size())). Then, it iterates backwards - * over the list until the specified element is found, or the beginning of - * the list is reached. - * - * @param o element to search for. - * - * @return the index in this list of the last occurence of the specified - * element, or -1 if the list does not contain this element. - */ - public int lastIndexOf(Object o) { - ListIterator e = listIterator(size()); - if (o==null) { - while (e.hasPrevious()) - if (e.previous()==null) - return e.nextIndex(); - } else { - while (e.hasPrevious()) - if (o.equals(e.previous())) - return e.nextIndex(); - } - return -1; + public int lastIndexOf(Object value) { + ListIterator iterator = listIterator(size()); + while (iterator.hasPrevious()) { + Object candidate = iterator.previous(); + if (value == null ? candidate == null : value.equals(candidate)) { + return iterator.nextIndex(); + } + } + return -1; } - - // Bulk Operations - - /** - * Removes all of the elements from this collection (optional operation). - * The collection will be empty after this call returns (unless it throws - * an exception).

- * - * This implementation calls removeRange(0, size()).

- * - * Note that this implementation throws an - * UnsupportedOperationException unless remove(int - * index) or removeRange(int fromIndex, int toIndex) is - * overridden. - * - * @throws UnsupportedOperationException if the clear method is - * not supported by this Collection. - */ public void clear() { removeRange(0, size()); } - /** - * Inserts all of the elements in the specified collection into this list - * at the specified position (optional operation). Shifts the element - * currently at that position (if any) and any subsequent elements to the - * right (increases their indices). The new elements will appear in the - * list in the order that they are returned by the specified collection's - * iterator. The behavior of this operation is unspecified if the - * specified collection is modified while the operation is in progress. - * (Note that this will occur if the specified collection is this list, - * and it's nonempty.)

- * - * This implementation gets an iterator over the specified collection and - * iterates over it, inserting the elements obtained from the iterator - * into this list at the appropriate position, one at a time, using - * add(int, Object). Many implementations will override this - * method for efficiency.

- * - * Note that this implementation throws an - * UnsupportedOperationException unless add(int, Object) - * is overridden. - * - * @return true if this list changed as a result of the call. - * @param index index at which to insert the first element from the - * specified collection. - * @param c elements to be inserted into this List. - * - * @throws UnsupportedOperationException if the addAll method is - * not supported by this list. - * - * @throws ClassCastException if the class of an element of the specified - * collection prevents it from being added to this List. - * - * @throws IllegalArgumentException some aspect an element of the - * specified collection prevents it from being added to this - * List. - * - * @throws IndexOutOfBoundsException index out of range (index < 0 - * || index > size()). - */ - public boolean addAll(int index, Collection c) { - boolean modified = false; - Iterator e = c.iterator(); - while (e.hasNext()) { - add(index++, e.next()); - modified = true; - } - return modified; + public boolean addAll(int index, Collection collection) { + rangeCheckForAdd(index); + boolean changed = false; + for (Object value : collection) { + add(index++, value); + changed = true; + } + return changed; } - - // Iterators - - /** - * Returns an iterator over the elements in this list in proper - * sequence.

- * - * This implementation returns a straightforward implementation of the - * iterator interface, relying on the backing list's size(), - * get(int), and remove(int) methods.

- * - * Note that the iterator returned by this method will throw an - * UnsupportedOperationException in response to its - * remove method unless the list's remove(int) method is - * overridden.

- * - * This implementation can be made to throw runtime exceptions in the face - * of concurrent modification, as described in the specification for the - * (protected) modCount field. - * - * @return an iterator over the elements in this list in proper sequence. - * - * @see #modCount - */ public Iterator iterator() { - return new Itr(); + return listIterator(); } - /** - * Returns an iterator of the elements in this list (in proper sequence). - * This implementation returns listIterator(0). - * - * @return an iterator of the elements in this list (in proper sequence). - * - * @see #listIterator(int) - */ public ListIterator listIterator() { - return listIterator(0); + return listIterator(0); } - /** - * Returns a list iterator of the elements in this list (in proper - * sequence), starting at the specified position in the list. The - * specified index indicates the first element that would be returned by - * an initial call to the next method. An initial call to - * the previous method would return the element with the - * specified index minus one.

- * - * This implementation returns a straightforward implementation of the - * ListIterator interface that extends the implementation of the - * Iterator interface returned by the iterator() method. - * The ListIterator implementation relies on the backing list's - * get(int), set(int, Object), add(int, Object) - * and remove(int) methods.

- * - * Note that the list iterator returned by this implementation will throw - * an UnsupportedOperationException in response to its - * remove, set and add methods unless the - * list's remove(int), set(int, Object), and - * add(int, Object) methods are overridden.

- * - * This implementation can be made to throw runtime exceptions in the - * face of concurrent modification, as described in the specification for - * the (protected) modCount field. - * - * @param index index of the first element to be returned from the list - * iterator (by a call to the next method). - * - * @return a list iterator of the elements in this list (in proper - * sequence), starting at the specified position in the list. - * - * @throws IndexOutOfBoundsException if the specified index is out of - * range (index < 0 || index > size()). - * - * @see #modCount - */ public ListIterator listIterator(final int index) { - if (index<0 || index>size()) - throw new IndexOutOfBoundsException("Index: "+index); + rangeCheckForAdd(index); + return new ListIterator() { + private int cursor = index; + private int lastReturned = -1; + private int expectedModCount = modCount; - return new ListItr(index); - } + public boolean hasNext() { return cursor < size(); } + public boolean hasPrevious() { return cursor > 0; } + public int nextIndex() { return cursor; } + public int previousIndex() { return cursor - 1; } - private class Itr implements Iterator { - /** - * Index of element to be returned by subsequent call to next. - */ - int cursor = 0; - - /** - * Index of element returned by most recent call to next or - * previous. Reset to -1 if this element is deleted by a call - * to remove. - */ - int lastRet = -1; - - /** - * The modCount value that the iterator believes that the backing - * List should have. If this expectation is violated, the iterator - * has detected concurrent modification. - */ - int expectedModCount = modCount; - - public boolean hasNext() { - return cursor != size(); - } - - public Object next() { - try { - Object next = get(cursor); - checkForComodification(); - lastRet = cursor++; - return next; - } catch(IndexOutOfBoundsException e) { - checkForComodification(); - throw new NoSuchElementException(); - } - } - - public void remove() { - if (lastRet == -1) - throw new IllegalStateException(); - checkForComodification(); - - try { - MyAbstractList.this.remove(lastRet); - if (lastRet < cursor) - cursor--; - lastRet = -1; - expectedModCount = modCount; - } catch(IndexOutOfBoundsException e) { - throw new ConcurrentModificationException(); - } - } - - final void checkForComodification() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - } - } + public Object next() { + checkForComodification(); + if (!hasNext()) throw new NoSuchElementException(); + Object value = get(cursor); + lastReturned = cursor++; + return value; + } - private class ListItr extends Itr implements ListIterator { - ListItr(int index) { - cursor = index; - } - - public boolean hasPrevious() { - return cursor != 0; - } - - public Object previous() { - try { - Object previous = get(--cursor); - checkForComodification(); - lastRet = cursor; - return previous; - } catch(IndexOutOfBoundsException e) { - checkForComodification(); - throw new NoSuchElementException(); - } - } - - public int nextIndex() { - return cursor; - } - - public int previousIndex() { - return cursor-1; - } - - public void set(Object o) { - if (lastRet == -1) - throw new IllegalStateException(); - checkForComodification(); - - try { - MyAbstractList.this.set(lastRet, o); - expectedModCount = modCount; - } catch(IndexOutOfBoundsException e) { - throw new ConcurrentModificationException(); - } - } - - public void add(Object o) { - checkForComodification(); - - try { - MyAbstractList.this.add(cursor++, o); - lastRet = -1; - expectedModCount = modCount; - } catch(IndexOutOfBoundsException e) { - throw new ConcurrentModificationException(); - } - } + public Object previous() { + checkForComodification(); + if (!hasPrevious()) throw new NoSuchElementException(); + Object value = get(--cursor); + lastReturned = cursor; + return value; + } + + public void remove() { + checkForComodification(); + if (lastReturned < 0) throw new IllegalStateException(); + MyAbstractList.this.remove(lastReturned); + if (lastReturned < cursor) cursor--; + lastReturned = -1; + expectedModCount = modCount; + } + + public void set(Object value) { + checkForComodification(); + if (lastReturned < 0) throw new IllegalStateException(); + MyAbstractList.this.set(lastReturned, value); + } + + public void add(Object value) { + checkForComodification(); + MyAbstractList.this.add(cursor++, value); + lastReturned = -1; + expectedModCount = modCount; + } + + private void checkForComodification() { + if (expectedModCount != modCount) { + throw new ConcurrentModificationException(); + } + } + }; } - /** - * Returns a view of the portion of this list between fromIndex, - * inclusive, and toIndex, exclusive. (If fromIndex and - * toIndex are equal, the returned list is empty.) The returned - * list is backed by this list, so changes in the returned list are - * reflected in this list, and vice-versa. The returned list supports all - * of the optional list operations supported by this list.

- * - * This method eliminates the need for explicit range operations (of the - * sort that commonly exist for arrays). Any operation that expects a - * list can be used as a range operation by operating on a subList view - * instead of a whole list. For example, the following idiom removes a - * range of elements from a list: - *

-     *     list.subList(from, to).clear();
-     * 
- * Similar idioms may be constructed for indexOf and - * lastIndexOf, and all of the algorithms in the - * Collections class can be applied to a subList.

- * - * The semantics of the list returned by this method become undefined if - * the backing list (i.e., this list) is structurally modified in - * any way other than via the returned list. (Structural modifications are - * those that change the size of the list, or otherwise perturb it in such - * a fashion that iterations in progress may yield incorrect results.)

- * - * This implementation returns a list that subclasses - * MyAbstractList. The subclass stores, in private fields, the - * offset of the subList within the backing list, the size of the subList - * (which can change over its lifetime), and the expected - * modCount value of the backing list.

- * - * The subclass's set(int, Object), get(int), - * add(int, Object), remove(int), addAll(int, - * Collection) and removeRange(int, int) methods all - * delegate to the corresponding methods on the backing abstract list, - * after bounds-checking the index and adjusting for the offset. The - * addAll(Collection c) method merely returns addAll(size, - * c).

- * - * The listIterator(int) method returns a "wrapper object" over a - * list iterator on the backing list, which is created with the - * corresponding method on the backing list. The iterator method - * merely returns listIterator(), and the size method - * merely returns the subclass's size field.

- * - * All methods first check to see if the actual modCount of the - * backing list is equal to its expected value, and throw a - * ConcurrentModificationException if it is not. - * - * @param fromIndex low endpoint (inclusive) of the subList. - * @param toIndex high endpoint (exclusive) of the subList. - * @return a view of the specified range within this list. - * @throws IndexOutOfBoundsException endpoint index value out of range - * (fromIndex < 0 || toIndex > size) - * @throws IllegalArgumentException endpoint indices out of order - * (fromIndex > toIndex) */ public List subList(int fromIndex, int toIndex) { return new SubList(this, fromIndex, toIndex); } - // Comparison and hashing - - /** - * Compares the specified object with this list for equality. Returns - * true if and only if the specified object is also a list, both - * lists have the same size, and all corresponding pairs of elements in - * the two lists are equal. (Two elements e1 and - * e2 are equal if (e1==null ? e2==null : - * e1.equals(e2)).) In other words, two lists are defined to be - * equal if they contain the same elements in the same order.

- * - * This implementation first checks if the specified object is this - * list. If so, it returns true; if not, it checks if the - * specified object is a list. If not, it returns false; if so, - * it iterates over both lists, comparing corresponding pairs of elements. - * If any comparison returns false, this method returns - * false. If either iterator runs out of elements before the - * other it returns false (as the lists are of unequal length); - * otherwise it returns true when the iterations complete. - * - * @param o the object to be compared for equality with this list. - * - * @return true if the specified object is equal to this list. - */ - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof List)) - return false; - - ListIterator e1 = listIterator(); - ListIterator e2 = ((List) o).listIterator(); - while(e1.hasNext() && e2.hasNext()) { - Object o1 = e1.next(); - Object o2 = e2.next(); - if (!(o1==null ? o2==null : o1.equals(o2))) - return false; - } - return !(e1.hasNext() || e2.hasNext()); + public boolean equals(Object other) { + if (other == this) return true; + if (!(other instanceof List)) return false; + Iterator left = iterator(); + Iterator right = ((List) other).iterator(); + while (left.hasNext() && right.hasNext()) { + Object a = left.next(); + Object b = right.next(); + if (!(a == null ? b == null : a.equals(b))) return false; + } + return !left.hasNext() && !right.hasNext(); } - /** - * Returns the hash code value for this list.

- * - * This implementation uses exactly the code that is used to define the - * list hash function in the documentation for the List.hashCode - * method. - * - * @return the hash code value for this list. - */ public int hashCode() { - int hashCode = 1; - Iterator i = iterator(); - while (i.hasNext()) { - Object obj = i.next(); - hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); - } - return hashCode; + int hash = 1; + for (Object value : this) { + hash = 31 * hash + (value == null ? 0 : value.hashCode()); + } + return hash; } - /** - * Removes from this list all of the elements whose index is between - * fromIndex, inclusive, and toIndex, exclusive. - * Shifts any succeeding elements to the left (reduces their index). This - * call shortens the ArrayList by (toIndex - fromIndex) - * elements. (If toIndex==fromIndex, this operation has no - * effect.)

- * - * This method is called by the clear operation on this list - * and its subLists. Overriding this method to take advantage of - * the internals of the list implementation can substantially - * improve the performance of the clear operation on this list - * and its subLists.

- * - * This implementation gets a list iterator positioned before - * fromIndex, and repeatedly calls ListIterator.next - * followed by ListIterator.remove until the entire range has - * been removed. Note: if ListIterator.remove requires linear - * time, this implementation requires quadratic time. - * - * @param fromIndex index of first element to be removed. - * @param toIndex index after last element to be removed. - */ protected void removeRange(int fromIndex, int toIndex) { - ListIterator it = listIterator(fromIndex); - for (int i=0, n=toIndex-fromIndex; istructurally modified. - * Structural modifications are those that change the size of the - * list, or otherwise perturb it in such a fashion that iterations in - * progress may yield incorrect results.

- * - * This field is used by the iterator and list iterator implementation - * returned by the iterator and listIterator methods. - * If the value of this field changes unexpectedly, the iterator (or list - * iterator) will throw a ConcurrentModificationException in - * response to the next, remove, previous, - * set or add operations. This provides - * fail-fast behavior, rather than non-deterministic behavior in - * the face of concurrent modification during iteration.

- * - * Use of this field by subclasses is optional. If a subclass - * wishes to provide fail-fast iterators (and list iterators), then it - * merely has to increment this field in its add(int, Object) and - * remove(int) methods (and any other methods that it overrides - * that result in structural modifications to the list). A single call to - * add(int, Object) or remove(int) must add no more than - * one to this field, or the iterators (and list iterators) will throw - * bogus ConcurrentModificationExceptions. If an implementation - * does not wish to provide fail-fast iterators, this field may be - * ignored. - */ - protected transient int modCount = 0; + private void rangeCheckForAdd(int index) { + if (index < 0 || index > size()) { + throw new IndexOutOfBoundsException( + "Index: " + index + ", Size: " + size()); + } + } } +/** Retained package-level compatibility type for historic callers. */ class SubList extends MyAbstractList { - private MyAbstractList l; - private int offset; - private int size; - private int expectedModCount; + private final MyAbstractList parent; + private final int offset; + private int length; + private int expectedParentModCount; SubList(MyAbstractList list, int fromIndex, int toIndex) { - if (fromIndex < 0) - throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); - if (toIndex > list.size()) - throw new IndexOutOfBoundsException("toIndex = " + toIndex); - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex(" + fromIndex + - ") > toIndex(" + toIndex + ")"); - l = list; + if (fromIndex < 0 || toIndex > list.size()) { + throw new IndexOutOfBoundsException(); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException(); + } + parent = list; offset = fromIndex; - size = toIndex - fromIndex; - expectedModCount = l.modCount; - } - - public Object set(int index, Object element) { - rangeCheck(index); - checkForComodification(); - return l.set(index+offset, element); + length = toIndex - fromIndex; + expectedParentModCount = parent.modCount; } public Object get(int index) { rangeCheck(index); checkForComodification(); - return l.get(index+offset); + return parent.get(offset + index); } public int size() { checkForComodification(); - return size; + return length; } - public void add(int index, Object element) { - if (index<0 || index>size) - throw new IndexOutOfBoundsException(); - checkForComodification(); - l.add(index+offset, element); - expectedModCount = l.modCount; - size++; - modCount++; - } - - public Object remove(int index) { + public Object set(int index, Object value) { rangeCheck(index); checkForComodification(); - Object result = l.remove(index+offset); - expectedModCount = l.modCount; - size--; - modCount++; - return result; + return parent.set(offset + index, value); } - protected void removeRange(int fromIndex, int toIndex) { + public void add(int index, Object value) { + if (index < 0 || index > length) throw new IndexOutOfBoundsException(); checkForComodification(); - l.removeRange(fromIndex+offset, toIndex+offset); - expectedModCount = l.modCount; - size -= (toIndex-fromIndex); - modCount++; + parent.add(offset + index, value); + length++; + modified(); } - public boolean addAll(Collection c) { - return addAll(size, c); - } - - public boolean addAll(int index, Collection c) { - if (index<0 || index>size) - throw new IndexOutOfBoundsException( - "Index: "+index+", Size: "+size); - int cSize = c.size(); - if (cSize==0) - return false; - + public Object remove(int index) { + rangeCheck(index); checkForComodification(); - l.addAll(offset+index, c); - expectedModCount = l.modCount; - size += cSize; - modCount++; - return true; + Object removed = parent.remove(offset + index); + length--; + modified(); + return removed; } - public Iterator iterator() { - return listIterator(); + public boolean addAll(Collection collection) { + return addAll(length, collection); } - public ListIterator listIterator(final int index) { + public boolean addAll(int index, Collection collection) { + if (index < 0 || index > length) throw new IndexOutOfBoundsException(); checkForComodification(); - if (index<0 || index>size) - throw new IndexOutOfBoundsException( - "Index: "+index+", Size: "+size); - - return new ListIterator() { - private ListIterator i = l.listIterator(index+offset); - - public boolean hasNext() { - return nextIndex() < size; - } - - public Object next() { - if (hasNext()) - return i.next(); - else - throw new NoSuchElementException(); - } - - public boolean hasPrevious() { - return previousIndex() >= 0; - } - - public Object previous() { - if (hasPrevious()) - return i.previous(); - else - throw new NoSuchElementException(); - } - - public int nextIndex() { - return i.nextIndex() - offset; - } - - public int previousIndex() { - return i.previousIndex() - offset; - } - - public void remove() { - i.remove(); - expectedModCount = l.modCount; - size--; - modCount++; - } - - public void set(Object o) { - i.set(o); - } - - public void add(Object o) { - i.add(o); - expectedModCount = l.modCount; - size++; - modCount++; - } - }; - } - - public List subList(int fromIndex, int toIndex) { - return new SubList(this, fromIndex, toIndex); + if (collection.isEmpty()) return false; + parent.addAll(offset + index, collection); + length += collection.size(); + modified(); + return true; } private void rangeCheck(int index) { - if (index<0 || index>=size) - throw new IndexOutOfBoundsException("Index: "+index+ - ",Size: "+size); + if (index < 0 || index >= length) throw new IndexOutOfBoundsException(); } private void checkForComodification() { - if (l.modCount != expectedModCount) + if (parent.modCount != expectedParentModCount) { throw new ConcurrentModificationException(); + } } -} + private void modified() { + expectedParentModCount = parent.modCount; + modCount++; + } +} diff --git a/src/main/java/com/lambda/Debugger/MyCollections.java b/src/main/java/com/lambda/Debugger/MyCollections.java index 9a0cd6d..ef0d77c 100644 --- a/src/main/java/com/lambda/Debugger/MyCollections.java +++ b/src/main/java/com/lambda/Debugger/MyCollections.java @@ -1,1709 +1,112 @@ -/* MyCollections.java - - Copyright 2003, Bil Lewis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* - * @(#)MyCollections.java 1.34 99/04/22 + /* + * Copyright 2003, Bil Lewis * - * Copyright 1997-1999 by Sun Microsystems, Inc., - * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. - * All rights reserved. - * - * This software is the confidential and proprietary information - * of Sun Microsystems, Inc. ("Confidential Information"). You - * shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement - * you entered into with Sun. + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any later + * version. */ - -//package java.util; package com.lambda.Debugger; -import java.util.*; -import java.io.Serializable; - -/** - * This class consists exclusively of static methods that operate on or return - * collections. It contains polymorphic algorithms that operate on - * collections, "wrappers", which return a new collection backed by a - * specified collection, and a few other odds and ends.

- * - * The documentation for the polymorphic algorithms contained in this class - * generally includes a brief description of the implementation. Such - * descriptions should be regarded as implementation notes, rather than - * parts of the specification. Implementors should feel free to - * substitute other algorithms, so long as the specification itself is adhered - * to. (For example, the algorithm used by sort does not have to be - * a mergesort, but it does have to be stable.) - * - * @author Josh Bloch - * @version 1.34 04/22/99 - * @see Collection - * @see Set - * @see List - * @see Map - * @since JDK1.2 - */ +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** Retained ODB compatibility facade over {@link Collections}. */ public class MyCollections { - // Suppresses default constructor, ensuring non-instantiability. - private MyCollections() { - } - - // Algorithms + public static final Set EMPTY_SET = Collections.EMPTY_SET; + public static final List EMPTY_LIST = Collections.EMPTY_LIST; - /** - * Sorts the specified list into ascending order, according to the - * natural ordering of its elements. All elements in the list must - * implement the Comparable interface. Furthermore, all elements - * in the list must be mutually comparable (that is, - * e1.compareTo(e2) must not throw a ClassCastException - * for any elements e1 and e2 in the list).

- * - * This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort.

- * - * The specified list must be modifiable, but need not be resizable.

- * - * The sorting algorithm is a modified mergesort (in which the merge is - * omitted if the highest element in the low sublist is less than the - * lowest element in the high sublist). This algorithm offers guaranteed - * n log(n) performance, and can approach linear performance on nearly - * sorted lists.

- * - * This implementation dumps the specified list into an array, sorts - * the array, and iterates over the list resetting each element - * from the corresponding position in the array. This avoids the - * n2 log(n) performance that would result from attempting - * to sort a linked list in place. - * - * @param list the list to be sorted. - * @throws ClassCastException if the list contains elements that are not - * mutually comparable (for example, strings and integers). - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - * @see Comparable - */ - public static void sort(List list) { - Object a[] = list.toArray(); - Arrays.sort(a); - ListIterator i = list.listIterator(); - for (int j=0; jmutually - * comparable using the specified comparator (that is, - * c.compare(e1, e2) must not throw a ClassCastException - * for any elements e1 and e2 in the list).

- * - * This sort is guaranteed to be stable: equal elements will - * not be reordered as a result of the sort.

- * - * The sorting algorithm is a modified mergesort (in which the merge is - * omitted if the highest element in the low sublist is less than the - * lowest element in the high sublist). This algorithm offers guaranteed - * n log(n) performance, and can approach linear performance on nearly - * sorted lists.

- * - * The specified list must be modifiable, but need not be resizable. - * This implementation dumps the specified list into an array, sorts - * the array, and iterates over the list resetting each element - * from the corresponding position in the array. This avoids the - * n2 log(n) performance that would result from attempting - * to sort a linked list in place. - * - * @param list the list to be sorted. - * @param c the comparator to determine the order of the array. - * @throws ClassCastException if the list contains elements that are not - * mutually comparable using the specified comparator. - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - * @see Comparator - */ - public static void sort(List list, Comparator c) { - Object a[] = list.toArray(); - Arrays.sort(a, c); - ListIterator i = list.listIterator(); - for (int j=0; jnatural ordering of its elements (as by the - * sort(List) method, above) prior to making this call. If it is - * not sorted, the results are undefined. If the list contains multiple - * elements equal to the specified object, there is no guarantee which one - * will be found.

- * - * This method runs in log(n) time for a "random access" list (which - * provides near-constant-time positional access). It may - * run in n log(n) time if it is called on a "sequential access" list - * (which provides linear-time positional access).

- * - * If the specified list implements the AbstracSequentialList - * interface, this method will do a sequential search instead of a binary - * search; this offers linear performance instead of n log(n) performance - * if this method is called on a LinkedList object. - * - * @param list the list to be searched. - * @param key the key to be searched for. - * @return index of the search key, if it is contained in the list; - * otherwise, (-(insertion point) - 1). The - * insertion point is defined as the point at which the - * key would be inserted into the list: the index of the first - * element greater than the key, or list.size(), if all - * elements in the list are less than the specified key. Note - * that this guarantees that the return value will be >= 0 if - * and only if the key is found. - * @throws ClassCastException if the list contains elements that are not - * mutually comparable (for example, strings and - * integers), or the search key in not mutually comparable - * with the elements of the list. - * @see Comparable - * @see #sort(List) - */ public static int binarySearch(List list, Object key) { - // Do a sequential search if appropriate - if (list instanceof AbstractSequentialList) { - ListIterator i = list.listIterator(); - while (i.hasNext()) { - int cmp = ((Comparable)(i.next())).compareTo(key); - if (cmp == 0) - return i.previousIndex(); - else if (cmp > 0) - return -i.nextIndex(); // key not found. - } - return -i.nextIndex()-1; // key not found, list exhausted - } - - // Otherwise, do a binary search - int low = 0; - int high = list.size()-1; - - while (low <= high) { - int mid =(low + high)/2; - Object midVal = list.get(mid); - int cmp = ((Comparable)midVal).compareTo(key); - - if (cmp < 0) - low = mid + 1; - else if (cmp > 0) - high = mid - 1; - else - return mid; // key found - } - return -(low + 1); // key not found - } - - /** - * Searches the specified list for the specified object using the binary - * search algorithm. The list must be sorted into ascending order - * according to the specified comparator (as by the Sort(List, - * Comparator) method, above), prior to making this call. If it is - * not sorted, the results are undefined. If the list contains multiple - * elements equal to the specified object, there is no guarantee which one - * will be found.

- * - * This method runs in log(n) time for a "random access" list (which - * provides near-constant-time positional access). It may - * run in n log(n) time if it is called on a "sequential access" list - * (which provides linear-time positional access).

- * - * If the specified list implements the AbstracSequentialList - * interface, this method will do a sequential search instead of a binary - * search; this offers linear performance instead of n log(n) performance - * if this method is called on a LinkedList object. - * - * @param list the list to be searched. - * @param key the key to be searched for. - * @param c the comparator by which the list is ordered. - * @return index of the search key, if it is contained in the list; - * otherwise, (-(insertion point) - 1). The - * insertion point is defined as the point at which the - * key would be inserted into the list: the index of the first - * element greater than the key, or list.size(), if all - * elements in the list are less than the specified key. Note - * that this guarantees that the return value will be >= 0 if - * and only if the key is found. - * @throws ClassCastException if the list contains elements that are not - * mutually comparable using the specified comparator, - * or the search key in not mutually comparable with the - * elements of the list using this comparator. - * @see Comparable - * @see #sort(List, Comparator) - */ - public static int binarySearch(List list, Object key, Comparator c) { - // Do a sequential search if appropriate - if (list instanceof AbstractSequentialList) { - ListIterator i = list.listIterator(); - while (i.hasNext()) { - int cmp = c.compare(i.next(), key); - if (cmp == 0) - return i.previousIndex(); - else if (cmp > 0) - return -i.nextIndex(); // key not found. - } - return -i.nextIndex()-1; // key not found, list exhausted - } - - // Otherwise, do a binary search - int low = 0; - int high = list.size()-1; - - while (low <= high) { - int mid =(low + high)/2; - Object midVal = list.get(mid); - int cmp = c.compare(midVal, key); - - if (cmp < 0) - low = mid + 1; - else if (cmp > 0) - high = mid - 1; - else - return mid; // key found - } - return -(low + 1); // key not found - } - - /** - * Reverses the order of the elements in the specified list.

- * - * This method runs in linear time. - * @param l the list whose elements are to be reversed. - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - */ - public static void reverse(List l) { - ListIterator fwd = l.listIterator(), rev = l.listIterator(l.size()); - for (int i=0, n=l.size()/2; i - * - * The hedge "approximately" is used in the foregoing description because - * default source of randomenss is only approximately an unbiased source - * of independently chosen bits. If it were a perfect source of randomly - * chosen bits, then the algorithm would choose permutations with perfect - * uniformity.

- * - * This implementation traverses the list backwards, from the last element - * up to the second, repeatedly swapping a randomly selected element into - * the "current position". Elements are randomly selected from the - * portion of the list that runs from the first element to the current - * position, inclusive.

- * - * This method runs in linear time for a "random access" list (which - * provides near-constant-time positional access). It may require - * quadratic time for a "sequential access" list. - * - * @param list the list to be shuffled. - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - */ - public static void shuffle(List list) { - shuffle(list, r); - } - private static Random r = new Random(); - - /** - * Randomly permute the specified list using the specified source of - * randomness. All permutations occur with equal likelihood - * assuming that the source of randomness is fair.

- * - * This implementation traverses the list backwards, from the last element - * up to the second, repeatedly swapping a randomly selected element into - * the "current position". Elements are randomly selected from the - * portion of the list that runs from the first element to the current - * position, inclusive.

- * - * This method runs in linear time for a "random access" list (which - * provides near-constant-time positional access). It may require - * quadratic time for a "sequential access" list. - * - * @param list the list to be shuffled. - * @param rnd the source of randomness to use to shuffle the list. - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - */ - public static void shuffle(List list, Random rnd) { - for (int i=list.size(); i>1; i--) - swap(list, i-1, rnd.nextInt(i)); - } - - /** - * Swaps the two specified elements in the specified list. - */ - private static void swap(List a, int i, int j) { - Object tmp = a.get(i); - a.set(i, a.get(j)); - a.set(j, tmp); - } - - /** - * Replaces all of the elements of the specified list with the specified - * element.

- * - * This method runs in linear time. - * - * @param list the list to be filled with the specified element. - * @param o The element with which to fill the specified list. - * @throws UnsupportedOperationException if the specified list's - * list-iterator does not support the set operation. - */ - public static void fill(List list, Object o) { - for (ListIterator i = list.listIterator(); i.hasNext(); ) { - i.next(); - i.set(o); - } - } - - /** - * Copies all of the elements from one list into another. After the - * operation, the index of each copied element in the destination list - * will be identical to its index in the source list. The destination - * list must be at least as long as the source list. If it is longer, the - * remaining elements in the destination list are unaffected.

- * - * This method runs in linear time. - * - * @param dest The destination list. - * @param src The source list. - * @throws IndexOutOfBoundsException if the destination list is too small - * to contain the entire source List. - * @throws UnsupportedOperationException if the destination list's - * list-iterator does not support the set operation. - */ - public static void copy (List dest, List src) { - try { - for (ListIterator di=dest.listIterator(), si=src.listIterator(); - si.hasNext(); ) { - di.next(); - di.set(si.next()); - } - } catch(NoSuchElementException e) { - throw new IndexOutOfBoundsException("Source does not fit in dest."); - } + public static int binarySearch(List list, Object key, + Comparator comparator) { + return Collections.binarySearch(list, key, comparator); } - - /** - * Returns the minimum element of the given collection, according to the - * natural ordering of its elements. All elements in the - * collection must implement the Comparable interface. - * Furthermore, all elements in the collection must be mutually - * comparable (that is, e1.compareTo(e2) must not throw a - * ClassCastException for any elements e1 and - * e2 in the collection).

- * - * This method iterates over the entire collection, hence it requires - * time proportional to the size of the collection. - * - * @param coll the collection whose minimum element is to be determined. - * @return the minimum element of the given collection, according - * to the natural ordering of its elements. - * @throws ClassCastException if the collection contains elements that are - * not mutually comparable (for example, strings and - * integers). - * @throws NoSuchElementException if the collection is empty. - * @see Comparable - */ - public static Object min(Collection coll) { - Iterator i = coll.iterator(); - Comparable candidate = (Comparable)(i.next()); - while (i.hasNext()) { - Comparable next = (Comparable)(i.next()); - if (next.compareTo(candidate) < 0) - candidate = next; - } - return candidate; + public static void reverse(List list) { Collections.reverse(list); } + public static void shuffle(List list) { Collections.shuffle(list); } + public static void shuffle(List list, Random random) { + Collections.shuffle(list, random); } - - /** - * Returns the minimum element of the given collection, according to the - * order induced by the specified comparator. All elements in the - * collection must be mutually comparable by the specified - * comparator (that is, comp.compare(e1, e2) must not throw a - * ClassCastException for any elements e1 and - * e2 in the collection).

- * - * This method iterates over the entire collection, hence it requires - * time proportional to the size of the collection. - * - * @param coll the collection whose minimum element is to be determined. - * @param comp - * @return the minimum element of the given collection, according - * to the specified comparator. - * @throws ClassCastException if the collection contains elements that are - * not mutually comparable using the specified comparator. - * @throws NoSuchElementException if the collection is empty. - * @see Comparable - */ - public static Object min(Collection coll, Comparator comp) { - Iterator i = coll.iterator(); - Object candidate = i.next(); - while (i.hasNext()) { - Object next = i.next(); - if (comp.compare(next, candidate) < 0) - candidate = next; - } - return candidate; + public static void fill(List list, Object value) { + Collections.fill(list, value); } - - /** - * Returns the maximum element of the given collection, according to the - * natural ordering of its elements. All elements in the - * collection must implement the Comparable interface. - * Furthermore, all elements in the collection must be mutually - * comparable (that is, e1.compareTo(e2) must not throw a - * ClassCastException for any elements e1 and - * e2 in the collection).

- * - * This method iterates over the entire collection, hence it requires - * time proportional to the size of the collection. - * - * @param coll the collection whose maximum element is to be determined. - * @return the maximum element of the given collection, according - * to the natural ordering of its elements. - * @throws ClassCastException if the collection contains elements that are - * not mutually comparable (for example, strings and - * integers). - * @throws NoSuchElementException if the collection is empty. - * @see Comparable - */ - public static Object max(Collection coll) { - Iterator i = coll.iterator(); - Comparable candidate = (Comparable)(i.next()); - while (i.hasNext()) { - Comparable next = (Comparable)(i.next()); - if (next.compareTo(candidate) > 0) - candidate = next; - } - return candidate; + public static void copy(List destination, List source) { + Collections.copy(destination, source); } - - /** - * Returns the maximum element of the given collection, according to the - * order induced by the specified comparator. All elements in the - * collection must be mutually comparable by the specified - * comparator (that is, comp.compare(e1, e2) must not throw a - * ClassCastException for any elements e1 and - * e2 in the collection).

- * - * This method iterates over the entire collection, hence it requires - * time proportional to the size of the collection. - * - * @param coll the collection whose maximum element is to be determined. - * @param comp - * @return the maximum element of the given collection, according - * to the specified comparator. - * @throws ClassCastException if the collection contains elements that are - * not mutually comparable using the specified comparator. - * @throws NoSuchElementException if the collection is empty. - * @see Comparable - */ - public static Object max(Collection coll, Comparator comp) { - Iterator i = coll.iterator(); - Object candidate = i.next(); - while (i.hasNext()) { - Object next = i.next(); - if (comp.compare(next, candidate) > 0) - candidate = next; - } - return candidate; + public static Object min(Collection collection) { + return Collections.min(collection); } - - - // Unmodifiable Wrappers - - /** - * Returns an unmodifiable view of the specified collection. This method - * allows modules to provide users with "read-only" access to internal - * collections. Query operations on the returned collection "read through" - * to the specified collection, and attempts to modify the returned - * collection, whether direct or via its iterator, result in an - * UnsupportedOperationException.

- * - * The returned collection does not pass the hashCode and equals - * operations through to the backing collection, but relies on - * Object's equals and hashCode methods. This - * is necessary to preserve the contracts of these operations in the case - * that the backing collection is a set or a list.

- * - * The returned collection will be serializable if the specified collection - * is serializable. - * - * @param c the collection for which an unmodifiable view is to be - * returned. - * @return an unmodifiable view of the specified collection. - */ - public static Collection unmodifiableCollection(Collection c) { - return new UnmodifiableCollection(c); + public static Object min(Collection collection, Comparator comparator) { + return Collections.min(collection, comparator); } - - static class UnmodifiableCollection implements Collection, Serializable { - Collection c; - - UnmodifiableCollection(Collection c) {this.c = c;} - - public int size() {return c.size();} - public boolean isEmpty() {return c.isEmpty();} - public boolean contains(Object o) {return c.contains(o);} - public Object[] toArray() {return c.toArray();} - public Object[] toArray(Object[] a) {return c.toArray(a);} - - public Iterator iterator() { - return new Iterator() { - Iterator i = c.iterator(); - - public boolean hasNext() {return i.hasNext();} - public Object next() {return i.next();} - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public boolean add(Object o){ - throw new UnsupportedOperationException(); - } - public boolean remove(Object o) { - throw new UnsupportedOperationException(); - } - - public boolean containsAll(Collection coll) { - return c.containsAll(coll); - } - public boolean addAll(Collection coll) { - throw new UnsupportedOperationException(); - } - public boolean removeAll(Collection coll) { - throw new UnsupportedOperationException(); - } - public boolean retainAll(Collection coll) { - throw new UnsupportedOperationException(); - } - public void clear() { - throw new UnsupportedOperationException(); - } + public static Object max(Collection collection) { + return Collections.max(collection); } - - /** - * Returns an unmodifiable view of the specified set. This method allows - * modules to provide users with "read-only" access to internal sets. - * Query operations on the returned set "read through" to the specified - * set, and attempts to modify the returned set, whether direct or via its - * iterator, result in an UnsupportedOperationException.

- * - * The returned set will be serializable if the specified set - * is serializable. - * - * @param s the set for which an unmodifiable view is to be returned. - * @return an unmodifiable view of the specified set. - */ - - public static Set unmodifiableSet(Set s) { - return new UnmodifiableSet(s); + public static Object max(Collection collection, Comparator comparator) { + return Collections.max(collection, comparator); } - - static class UnmodifiableSet extends UnmodifiableCollection - implements Set, Serializable { - UnmodifiableSet(Set s) {super(s);} - - public boolean equals(Object o) {return c.equals(o);} - public int hashCode() {return c.hashCode();} + public static Collection unmodifiableCollection(Collection collection) { + return Collections.unmodifiableCollection(collection); } - - /** - * Returns an unmodifiable view of the specified sorted set. This method - * allows modules to provide users with "read-only" access to internal - * sorted sets. Query operations on the returned sorted set "read - * through" to the specified sorted set. Attempts to modify the returned - * sorted set, whether direct, via its iterator, or via its - * subSet, headSet, or tailSet views, result in - * an UnsupportedOperationException.

- * - * The returned sorted set will be serializable if the specified sorted set - * is serializable. - * - * @param s the sorted set for which an unmodifiable view is to be - * returned. - * @return an unmodifiable view of the specified sorted set. - */ - public static SortedSet unmodifiableSortedSet(SortedSet s) { - return new UnmodifiableSortedSet(s); + public static Set unmodifiableSet(Set set) { + return Collections.unmodifiableSet(set); } - - static class UnmodifiableSortedSet extends UnmodifiableSet - implements SortedSet, Serializable { - private SortedSet ss; - - UnmodifiableSortedSet(SortedSet s) {super(s); ss = s;} - - public Comparator comparator() {return ss.comparator();} - - public SortedSet subSet(Object fromElement, Object toElement) { - return new UnmodifiableSortedSet(ss.subSet(fromElement,toElement)); - } - public SortedSet headSet(Object toElement) { - return new UnmodifiableSortedSet(ss.headSet(toElement)); - } - public SortedSet tailSet(Object fromElement) { - return new UnmodifiableSortedSet(ss.tailSet(fromElement)); - } - - public Object first() {return ss.first();} - public Object last() {return ss.last();} + public static SortedSet unmodifiableSortedSet(SortedSet set) { + return Collections.unmodifiableSortedSet(set); } - - /** - * Returns an unmodifiable view of the specified list. This method allows - * modules to provide users with "read-only" access to internal - * lists. Query operations on the returned list "read through" to the - * specified list, and attempts to modify the returned list, whether - * direct or via its iterator, result in an - * UnsupportedOperationException.

- * - * The returned list will be serializable if the specified list - * is serializable. - * - * @param list the list for which an unmodifiable view is to be returned. - * @return an unmodifiable view of the specified list. - */ public static List unmodifiableList(List list) { - return new UnmodifiableList(list); - } - - static class UnmodifiableList extends UnmodifiableCollection - implements List { - private List list; - - UnmodifiableList(List list) { - super(list); - this.list = list; - } - - public boolean equals(Object o) {return list.equals(o);} - public int hashCode() {return list.hashCode();} - - public Object get(int index) {return list.get(index);} - public Object set(int index, Object element) { - throw new UnsupportedOperationException(); - } - public void add(int index, Object element) { - throw new UnsupportedOperationException(); - } - public Object remove(int index) { - throw new UnsupportedOperationException(); - } - public int indexOf(Object o) {return list.indexOf(o);} - public int lastIndexOf(Object o) {return list.lastIndexOf(o);} - public boolean addAll(int index, Collection c) { - throw new UnsupportedOperationException(); - } - public ListIterator listIterator() {return listIterator(0);} - - public ListIterator listIterator(final int index) { - return new ListIterator() { - ListIterator i = list.listIterator(index); - - public boolean hasNext() {return i.hasNext();} - public Object next() {return i.next();} - public boolean hasPrevious() {return i.hasPrevious();} - public Object previous() {return i.previous();} - public int nextIndex() {return i.nextIndex();} - public int previousIndex() {return i.previousIndex();} - - public void remove() { - throw new UnsupportedOperationException(); - } - public void set(Object o) { - throw new UnsupportedOperationException(); - } - public void add(Object o) { - throw new UnsupportedOperationException(); - } - }; - } - - public List subList(int fromIndex, int toIndex) { - return new UnmodifiableList(list.subList(fromIndex, toIndex)); - } - } - - /** - * Returns an unmodifiable view of the specified map. This method - * allows modules to provide users with "read-only" access to internal - * maps. Query operations on the returned map "read through" - * to the specified map, and attempts to modify the returned - * map, whether direct or via its collection views, result in an - * UnsupportedOperationException.

- * - * The returned map will be serializable if the specified map - * is serializable. - * - * @param m the map for which an unmodifiable view is to be returned. - * @return an unmodifiable view of the specified map. - */ - public static Map unmodifiableMap(Map m) { - return new UnmodifiableMap(m); - } - - private static class UnmodifiableMap implements Map, Serializable { - private final Map m; - - UnmodifiableMap(Map m) {this.m = m;} - - public int size() {return m.size();} - public boolean isEmpty() {return m.isEmpty();} - public boolean containsKey(Object key) {return m.containsKey(key);} - public boolean containsValue(Object val) {return m.containsValue(val);} - public Object get(Object key) {return m.get(key);} - - public Object put(Object key, Object value) { - throw new UnsupportedOperationException(); - } - public Object remove(Object key) { - throw new UnsupportedOperationException(); - } - public void putAll(Map t) { - throw new UnsupportedOperationException(); - } - public void clear() { - throw new UnsupportedOperationException(); - } - - private transient Set keySet = null; - private transient Set entrySet = null; - private transient Collection values = null; - - public Set keySet() { - if (keySet==null) - keySet = unmodifiableSet(m.keySet()); - return keySet; - } - - public Set entrySet() { - if (entrySet==null) - entrySet = new UnmodifiableEntrySet(m.entrySet()); - return entrySet; - } - - public Collection values() { - if (values==null) - values = unmodifiableCollection(m.values()); - return values; - } - - public boolean equals(Object o) {return m.equals(o);} - public int hashCode() {return m.hashCode();} - - - /** - * We need this class in addition to UnmodifiableSet as - * Map.Entries themselves permit modification of the backing Map - * via their setValue operation. This class is subtle: there are - * many possible attacks that must be thwarted. - */ - static class UnmodifiableEntrySet extends UnmodifiableSet { - UnmodifiableEntrySet(Set s) { - super(s); - } - - public Iterator iterator() { - return new Iterator() { - Iterator i = c.iterator(); - - public boolean hasNext() { - return i.hasNext(); - } - public Object next() { - return new UnmodifiableEntry((Map.Entry)i.next()); - } - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public Object[] toArray() { - Object[] a = c.toArray(); - for (int i=0; i a.length) - return arr; - - System.arraycopy(arr, 0, a, 0, arr.length); - if (a.length > arr.length) - a[arr.length] = null; - return a; - } - - /** - * This method is overridden to protect the backing set against - * an object with a nefarious equals function that senses - * that the equality-candidate is Map.Entry and calls its - * setValue method. - */ - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - return c.contains(new UnmodifiableEntry((Map.Entry)o)); - } - - /** - * The next two methods are overridden to protect against - * an unscrupulous List whose contains(Object o) method senses - * when o is a Map.Entry, and calls o.setValue. - */ - public boolean containsAll(Collection coll) { - Iterator e = coll.iterator(); - while (e.hasNext()) - if(!contains(e.next())) // Invokes safe contains() above - return false; - return true; - } - public boolean equals(Object o) { - if (o == this) - return true; - - if (!(o instanceof Set)) - return false; - Set s = (Set) o; - if (s.size() != c.size()) - return false; - return containsAll(s); // Invokes safe containsAll() above - } - - /** - * This "wrapper class" serves two purposes: it prevents - * the client from modifying the backing Map, by short-circuiting - * the setValue method, and it protects the backing Map against - * an ill-behaved Map.Entry that attempts to modify another - * Map Entry when asked to perform an equality check. - */ - private static class UnmodifiableEntry implements Map.Entry { - private Map.Entry e; - - UnmodifiableEntry(Map.Entry e) {this.e = e;} - - public Object getKey() {return e.getKey();} - public Object getValue() {return e.getValue();} - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } - public int hashCode() {return e.hashCode();} - public boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry t = (Map.Entry)o; - return eq(e.getKey(), t.getKey()) && - eq(e.getValue(), t.getValue()); - } - public String toString() {return e.toString();} - } - } - } - - /** - * Returns an unmodifiable view of the specified sorted map. This method - * allows modules to provide users with "read-only" access to internal - * sorted maps. Query operations on the returned sorted map "read through" - * to the specified sorted map. Attempts to modify the returned - * sorted map, whether direct, via its collection views, or via its - * subMap, headMap, or tailMap views, result in - * an UnsupportedOperationException.

- * - * The returned sorted map will be serializable if the specified sorted map - * is serializable. - * - * @param m the sorted map for which an unmodifiable view is to be - * returned. - * @return an unmodifiable view of the specified sorted map. - */ - public static SortedMap unmodifiableSortedMap(SortedMap m) { - return new UnmodifiableSortedMap(m); - } - - static class UnmodifiableSortedMap extends UnmodifiableMap - implements SortedMap, Serializable { - private SortedMap sm; - - UnmodifiableSortedMap(SortedMap m) {super(m); sm = m;} - - public Comparator comparator() {return sm.comparator();} - - public SortedMap subMap(Object fromKey, Object toKey) { - return new UnmodifiableSortedMap(sm.subMap(fromKey, toKey)); - } - public SortedMap headMap(Object toKey) { - return new UnmodifiableSortedMap(sm.headMap(toKey)); - } - public SortedMap tailMap(Object fromKey) { - return new UnmodifiableSortedMap(sm.tailMap(fromKey)); - } - - public Object firstKey() {return sm.firstKey();} - public Object lastKey() {return sm.lastKey();} - } - - - // Synch Wrappers - - /** - * Returns a synchronized (thread-safe) collection backed by the specified - * collection. In order to guarantee serial access, it is critical that - * all access to the backing collection is accomplished - * through the returned collection.

- * - * It is imperative that the user manually synchronize on the returned - * collection when iterating over it: - *

-     *  Collection c = MyCollections.synchronizedCollection(myCollection);
-     *     ...
-     *  synchronized(c) {
-     *      Iterator i = c.iterator(); // Must be in the synchronized block
-     *      while (i.hasNext())
-     *         foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned collection does not pass the hashCode - * and equals operations through to the backing collection, but - * relies on Object's equals and hashCode methods. This is - * necessary to preserve the contracts of these operations in the case - * that the backing collection is a set or a list.

- * - * The returned collection will be serializable if the specified collection - * is serializable. - * - * @param c the collection to be "wrapped" in a synchronized collection. - * @return a synchronized view of the specified collection. - */ - public static Collection synchronizedCollection(Collection c) { - return new SynchronizedCollection(c); - } - - static Collection synchronizedCollection(Collection c, Object mutex) { - return new SynchronizedCollection(c, mutex); - } - - static class SynchronizedCollection implements Collection, Serializable { - Collection c; // Backing Collection - Object mutex; // Object on which to synchronize - - SynchronizedCollection(Collection c) { - this.c = c; mutex = this; - } - SynchronizedCollection(Collection c, Object mutex) { - this.c = c; this.mutex = mutex; - } - - public int size() { - synchronized(mutex) {return c.size();} - } - public boolean isEmpty() { - synchronized(mutex) {return c.isEmpty();} - } - public boolean contains(Object o) { - synchronized(mutex) {return c.contains(o);} - } - public Object[] toArray() { - synchronized(mutex) {return c.toArray();} - } - public Object[] toArray(Object[] a) { - synchronized(mutex) {return c.toArray(a);} - } - - public Iterator iterator() { - return c.iterator(); // Must be manually synched by user! - } - - public boolean add(Object o) { - synchronized(mutex) {return c.add(o);} - } - public boolean remove(Object o) { - synchronized(mutex) {return c.remove(o);} - } - - public boolean containsAll(Collection coll) { - synchronized(mutex) {return c.containsAll(coll);} - } - public boolean addAll(Collection coll) { - synchronized(mutex) {return c.addAll(coll);} - } - public boolean removeAll(Collection coll) { - synchronized(mutex) {return c.removeAll(coll);} - } - public boolean retainAll(Collection coll) { - synchronized(mutex) {return c.retainAll(coll);} - } - public void clear() { - synchronized(mutex) {c.clear();} - } + return Collections.unmodifiableList(list); } - - /** - * Returns a synchronized (thread-safe) set backed by the specified - * set. In order to guarantee serial access, it is critical that - * all access to the backing set is accomplished - * through the returned set.

- * - * It is imperative that the user manually synchronize on the returned - * set when iterating over it: - *

-     *  Set s = MyCollections.synchronizedSet(new HashSet());
-     *      ...
-     *  synchronized(s) {
-     *      Iterator i = s.iterator(); // Must be in the synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned set will be serializable if the specified set is - * serializable. - * - * @param s the set to be "wrapped" in a synchronized set. - * @return a synchronized view of the specified set. - */ - public static Set synchronizedSet(Set s) { - return new SynchronizedSet(s); + public static Map unmodifiableMap(Map map) { + return Collections.unmodifiableMap(map); } - - static Set synchronizedSet(Set s, Object mutex) { - return new SynchronizedSet(s, mutex); + public static SortedMap unmodifiableSortedMap(SortedMap map) { + return Collections.unmodifiableSortedMap(map); } - - static class SynchronizedSet extends SynchronizedCollection - implements Set { - SynchronizedSet(Set s) { - super(s); - } - SynchronizedSet(Set s, Object mutex) { - super(s, mutex); - } - - public boolean equals(Object o) { - synchronized(mutex) {return c.equals(o);} - } - public int hashCode() { - synchronized(mutex) {return c.hashCode();} - } + public static Collection synchronizedCollection(Collection collection) { + return Collections.synchronizedCollection(collection); } - - /** - * Returns a synchronized (thread-safe) sorted set backed by the specified - * sorted set. In order to guarantee serial access, it is critical that - * all access to the backing sorted set is accomplished - * through the returned sorted set (or its views).

- * - * It is imperative that the user manually synchronize on the returned - * sorted set when iterating over it or any of its subSet, - * headSet, or tailSet views. - *

-     *  SortedSet s = MyCollections.synchronizedSortedSet(new HashSortedSet());
-     *      ...
-     *  synchronized(s) {
-     *      Iterator i = s.iterator(); // Must be in the synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * or: - *
-     *  SortedSet s = MyCollections.synchronizedSortedSet(new HashSortedSet());
-     *  SortedSet s2 = s.headSet(foo);
-     *      ...
-     *  synchronized(s) {  // Note: s, not s2!!!
-     *      Iterator i = s2.iterator(); // Must be in the synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned sorted set will be serializable if the specified - * sorted set is serializable. - * - * @param s the sorted set to be "wrapped" in a synchronized sorted set. - * @return a synchronized view of the specified sorted set. - */ - public static SortedSet synchronizedSortedSet(SortedSet s) { - return new SynchronizedSortedSet(s); + public static Set synchronizedSet(Set set) { + return Collections.synchronizedSet(set); } - - static class SynchronizedSortedSet extends SynchronizedSet - implements SortedSet - { - private SortedSet ss; - - SynchronizedSortedSet(SortedSet s) { - super(s); - ss = s; - } - SynchronizedSortedSet(SortedSet s, Object mutex) { - super(s, mutex); - ss = s; - } - - public Comparator comparator() { - synchronized(mutex) {return ss.comparator();} - } - - public SortedSet subSet(Object fromElement, Object toElement) { - synchronized(mutex) { - return new SynchronizedSortedSet( - ss.subSet(fromElement, toElement), mutex); - } - } - public SortedSet headSet(Object toElement) { - synchronized(mutex) { - return new SynchronizedSortedSet(ss.headSet(toElement), mutex); - } - } - public SortedSet tailSet(Object fromElement) { - synchronized(mutex) { - return new SynchronizedSortedSet(ss.tailSet(fromElement),mutex); - } - } - - public Object first() { - synchronized(mutex) {return ss.first();} - } - public Object last() { - synchronized(mutex) {return ss.last();} - } + public static SortedSet synchronizedSortedSet(SortedSet set) { + return Collections.synchronizedSortedSet(set); } - - /** - * Returns a synchronized (thread-safe) list backed by the specified - * list. In order to guarantee serial access, it is critical that - * all access to the backing list is accomplished - * through the returned list.

- * - * It is imperative that the user manually synchronize on the returned - * list when iterating over it: - *

-     *  List list = MyCollections.synchronizedList(new ArrayList());
-     *      ...
-     *  synchronized(list) {
-     *      Iterator i = list.iterator(); // Must be in synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned list will be serializable if the specified list is - * serializable. - * - * @param list the list to be "wrapped" in a synchronized list. - * @return a synchronized view of the specified list. - */ public static List synchronizedList(List list) { - return new SynchronizedList(list); - } - - static List synchronizedList(List list, Object mutex) { - return new SynchronizedList(list, mutex); - } - - static class SynchronizedList extends SynchronizedCollection - implements List { - private List list; - - SynchronizedList(List list) { - super(list); - this.list = list; - } - SynchronizedList(List list, Object mutex) { - super(list, mutex); - this.list = list; - } - - public boolean equals(Object o) { - synchronized(mutex) {return list.equals(o);} - } - public int hashCode() { - synchronized(mutex) {return list.hashCode();} - } - - public Object get(int index) { - synchronized(mutex) {return list.get(index);} - } - public Object set(int index, Object element) { - synchronized(mutex) {return list.set(index, element);} - } - public void add(int index, Object element) { - synchronized(mutex) {list.add(index, element);} - } - public Object remove(int index) { - synchronized(mutex) {return list.remove(index);} - } - - public int indexOf(Object o) { - synchronized(mutex) {return list.indexOf(o);} - } - public int lastIndexOf(Object o) { - synchronized(mutex) {return list.lastIndexOf(o);} - } - - public boolean addAll(int index, Collection c) { - synchronized(mutex) {return list.addAll(index, c);} - } - - public ListIterator listIterator() { - return list.listIterator(); // Must be manually synched by user - } - - public ListIterator listIterator(int index) { - return list.listIterator(index); // Must be manually synched by usr - } - - public List subList(int fromIndex, int toIndex) { - synchronized(mutex) { - return new SynchronizedList(list.subList(fromIndex, toIndex), - mutex); - } - } - } - - /** - * Returns a synchronized (thread-safe) map backed by the specified - * map. In order to guarantee serial access, it is critical that - * all access to the backing map is accomplished - * through the returned map.

- * - * It is imperative that the user manually synchronize on the returned - * map when iterating over any of its collection views: - *

-     *  Map m = MyCollections.synchronizedMap(new HashMap());
-     *      ...
-     *  Set s = m.keySet();  // Needn't be in synchronized block
-     *      ...
-     *  synchronized(m) {  // Synchronizing on m, not s!
-     *      Iterator i = s.iterator(); // Must be in synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned map will be serializable if the specified map is - * serializable. - * - * @param m the map to be "wrapped" in a synchronized map. - * @return a synchronized view of the specified map. - */ - public static Map synchronizedMap(Map m) { - return new SynchronizedMap(m); - } - - private static class SynchronizedMap implements Map, Serializable { - private Map m; // Backing Map - Object mutex; // Object on which to synchronize - - SynchronizedMap(Map m) { - this.m = m; mutex = this; - } - - SynchronizedMap(Map m, Object mutex) { - this.m = m; this.mutex = mutex; - } - - public int size() { - synchronized(mutex) {return m.size();} - } - public boolean isEmpty(){ - synchronized(mutex) {return m.isEmpty();} - } - public boolean containsKey(Object key) { - synchronized(mutex) {return m.containsKey(key);} - } - public boolean containsValue(Object value){ - synchronized(mutex) {return m.containsValue(value);} - } - public Object get(Object key) { - synchronized(mutex) {return m.get(key);} - } - - public Object put(Object key, Object value) { - synchronized(mutex) {return m.put(key, value);} - } - public Object remove(Object key) { - synchronized(mutex) {return m.remove(key);} - } - public void putAll(Map map) { - synchronized(mutex) {m.putAll(map);} - } - public void clear() { - synchronized(mutex) {m.clear();} - } - - private transient Set keySet = null; - private transient Set entrySet = null; - private transient Collection values = null; - - public Set keySet() { - synchronized(mutex) { - if (keySet==null) - keySet = new SynchronizedSet(m.keySet(), this); - return keySet; - } - } - - public Set entrySet() { - synchronized(mutex) { - if (entrySet==null) - entrySet = new SynchronizedSet(m.entrySet(), this); - return entrySet; - } - } - - public Collection values() { - synchronized(mutex) { - if (values==null) - values = new SynchronizedCollection(m.values(), this); - return values; - } - } - - public boolean equals(Object o) { - synchronized(mutex) {return m.equals(o);} - } - public int hashCode() { - synchronized(mutex) {return m.hashCode();} - } - } - - /** - * Returns a synchronized (thread-safe) sorted map backed by the specified - * sorted map. In order to guarantee serial access, it is critical that - * all access to the backing sorted map is accomplished - * through the returned sorted map (or its views).

- * - * It is imperative that the user manually synchronize on the returned - * sorted map when iterating over any of its collection views, or the - * collections views of any of its subMap, headMap or - * tailMap views. - *

-     *  SortedMap m = MyCollections.synchronizedSortedMap(new HashSortedMap());
-     *      ...
-     *  Set s = m.keySet();  // Needn't be in synchronized block
-     *      ...
-     *  synchronized(m) {  // Synchronizing on m, not s!
-     *      Iterator i = s.iterator(); // Must be in synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * or: - *
-     *  SortedMap m = MyCollections.synchronizedSortedMap(new HashSortedMap());
-     *  SortedMap m2 = m.subMap(foo, bar);
-     *      ...
-     *  Set s2 = m2.keySet();  // Needn't be in synchronized block
-     *      ...
-     *  synchronized(m) {  // Synchronizing on m, not m2 or s2!
-     *      Iterator i = s.iterator(); // Must be in synchronized block
-     *      while (i.hasNext())
-     *          foo(i.next());
-     *  }
-     * 
- * Failure to follow this advice may result in non-deterministic behavior. - * - *

The returned sorted map will be serializable if the specified - * sorted map is serializable. - * - * @param m the sorted map to be "wrapped" in a synchronized sorted map. - * @return a synchronized view of the specified sorted map. - */ - public static SortedMap synchronizedSortedMap(SortedMap m) { - return new SynchronizedSortedMap(m); - } - - - static class SynchronizedSortedMap extends SynchronizedMap - implements SortedMap - { - private SortedMap sm; - - SynchronizedSortedMap(SortedMap m) { - super(m); - sm = m; - } - SynchronizedSortedMap(SortedMap m, Object mutex) { - super(m, mutex); - sm = m; - } - - public Comparator comparator() { - synchronized(mutex) {return sm.comparator();} - } - - public SortedMap subMap(Object fromKey, Object toKey) { - synchronized(mutex) { - return new SynchronizedSortedMap( - sm.subMap(fromKey, toKey), mutex); - } - } - public SortedMap headMap(Object toKey) { - synchronized(mutex) { - return new SynchronizedSortedMap(sm.headMap(toKey), mutex); - } - } - public SortedMap tailMap(Object fromKey) { - synchronized(mutex) { - return new SynchronizedSortedMap(sm.tailMap(fromKey),mutex); - } - } - - public Object firstKey() { - synchronized(mutex) {return sm.firstKey();} - } - public Object lastKey() { - synchronized(mutex) {return sm.lastKey();} - } - } - - - // Miscellaneous - - /** - * The empty set (immutable). This set is serializable. - */ - public static final Set EMPTY_SET = new EmptySet(); - - private static class EmptySet extends AbstractSet implements Serializable { - public Iterator iterator() { - return new Iterator() { - public boolean hasNext() { - return false; - } - public Object next() { - throw new NoSuchElementException(); - } - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public int size() {return 0;} - - public boolean contains(Object obj) {return false;} - } - - /** - * The empty list (immutable). This list is serializable. - */ - public static final List EMPTY_LIST = new EmptyList(); - - private static class EmptyList extends AbstractList - implements Serializable { - public int size() {return 0;} - - public boolean contains(Object obj) {return false;} - - public Object get(int index) { - throw new IndexOutOfBoundsException("Index: "+index); - } + return Collections.synchronizedList(list); } - - /** - * Returns an immutable set containing only the specified object. - * The returned set is serializable. - * @param o - * - * @return an immutable set containing only the specified object. - */ - public static Set singleton(Object o) { - return new SingletonSet(o); + public static Map synchronizedMap(Map map) { + return Collections.synchronizedMap(map); } - - private static class SingletonSet extends AbstractSet - implements Serializable - { - private Object element; - - SingletonSet(Object o) {element = o;} - - public Iterator iterator() { - return new Iterator() { - private boolean hasNext = true; - public boolean hasNext() { - return hasNext; - } - public Object next() { - if (hasNext) { - hasNext = false; - return element; - } - throw new NoSuchElementException(); - } - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public int size() {return 1;} - - public boolean contains(Object o) {return eq(o, element);} + public static SortedMap synchronizedSortedMap(SortedMap map) { + return Collections.synchronizedSortedMap(map); } - - /** - * Returns an immutable list consisting of n copies of the - * specified object. The newly allocated data object is tiny (it contains - * a single reference to the data object). This method is useful in - * combination with the List.addAll method to grow lists. - * The returned list is serializable. - * - * @param n the number of elements in the returned list. - * @param o the element to appear repeatedly in the returned list. - * @return an immutable list consisting of n copies of the - * specified object. - * @throws IllegalArgumentException if n < 0. - * @see List#addAll(Collection) - * @see List#addAll(int, Collection) - */ - public static List nCopies(int n, Object o) { - return new CopiesList(n, o); + public static Set singleton(Object value) { + return Collections.singleton(value); } - - private static class CopiesList extends AbstractList - implements Serializable - { - int n; - Object element; - - CopiesList(int n, Object o) { - if (n < 0) - throw new IllegalArgumentException("List length = " + n); - this.n = n; - element = o; - } - - public int size() { - return n; - } - - public boolean contains(Object obj) { - return n != 0 && eq(obj, element); - } - - public Object get(int index) { - if (index<0 || index>=n) - throw new IndexOutOfBoundsException("Index: "+index+ - ", Size: "+n); - return element; - } + public static List nCopies(int count, Object value) { + return Collections.nCopies(count, value); } - - /** - * Returns a comparator that imposes the reverse of the natural - * ordering on a collection of objects that implement the - * Comparable interface. (The natural ordering is the ordering - * imposed by the objects' own compareTo method.) This enables a - * simple idiom for sorting (or maintaining) collections (or arrays) of - * objects that implement the Comparable interface in - * reverse-natural-order. For example, suppose a is an array of - * strings. Then:

-     * 		Arrays.sort(a, MyCollections.reverseOrder());
-     * 
sorts the array in reverse-lexicographic (alphabetical) order.

- * - * The returned comparator is serializable. - * - * @return a comparator that imposes the reverse of the natural - * ordering on a collection of objects that implement - * the Comparable interface. - * @see Comparable - */ public static Comparator reverseOrder() { - return REVERSE_ORDER; - } - - private static final Comparator REVERSE_ORDER = new ReverseComparator(); - - private static class ReverseComparator implements Comparator,Serializable { - public int compare(Object o1, Object o2) { - Comparable c1 = (Comparable)o1; - Comparable c2 = (Comparable)o2; - return -c1.compareTo(c2); - } - } - - /** - * Returns an enumeration over the specified collection. This provides - * interoperatbility with legacy APIs that require an enumeration - * as input. - * - * @param c the collection for which an enumeration is to be returned. - * @return an enumeration over the specified collection. - */ - public static Enumeration enumeration(final Collection c) { - return new Enumeration() { - Iterator i = c.iterator(); - - public boolean hasMoreElements() { - return i.hasNext(); - } - - public Object nextElement() { - return i.next(); - } - }; + return Collections.reverseOrder(); } - - /** - * Returns true if the specified arguments are equal, or both null. - */ - private static boolean eq(Object o1, Object o2) { - return (o1==null ? o2==null : o1.equals(o2)); + public static Enumeration enumeration(Collection collection) { + return Collections.enumeration(collection); } } diff --git a/src/main/java/com/lambda/Debugger/StopButton.java b/src/main/java/com/lambda/Debugger/StopButton.java index 902fe7f..d96015a 100644 --- a/src/main/java/com/lambda/Debugger/StopButton.java +++ b/src/main/java/com/lambda/Debugger/StopButton.java @@ -97,9 +97,16 @@ public void run() { try { SwingUtilities.invokeAndWait(r); } catch (InterruptedException ie) { - }// impossible - catch (java.lang.reflect.InvocationTargetException ie) { - }// impossible + if (IntegrationState.isActive()) { + Thread.currentThread().interrupt(); + throw IntegrationState.internalFailed("ODB controller startup was interrupted.", ie); + } + } catch (java.lang.reflect.InvocationTargetException ie) { + if (IntegrationState.isActive()) { + Throwable cause = ie.getCause() == null ? ie : ie.getCause(); + throw IntegrationState.internalFailed("Could not open the ODB controller.", cause); + } + } } public static void runButton(boolean startTarget, boolean paused, diff --git a/src/main/java/com/lambda/Debugger/TimeStamp.java b/src/main/java/com/lambda/Debugger/TimeStamp.java index 5e8b6b4..82ae37a 100644 --- a/src/main/java/com/lambda/Debugger/TimeStamp.java +++ b/src/main/java/com/lambda/Debugger/TimeStamp.java @@ -559,6 +559,7 @@ public final static int addStamp(int slIndex, int type, TraceLine tl) { istamps[index] = type | threadIndexUnshifted | slIndex; index++; nTSCreated++; + IntegrationState.timestampAdded(nTSCreated, eott()); return index-1; // return(addStamp(sl, type, threadIndexUnshifted)); @@ -600,6 +601,7 @@ public final static int addStamp(int slIndex, int type, int threadIndexUnshifted istamps[index] = type | threadIndexUnshifted | slIndex; index++; nTSCreated++; + IntegrationState.timestampAdded(nTSCreated, eott()); return index-1; } @@ -645,6 +647,11 @@ public static int getThreadIndex(Thread tid) { } } System.err.println("Too many threads. "+tid+" The debugger can only handle " + MAX_THREADS); + if (IntegrationState.isActive()) { + throw IntegrationState.internalFailed( + "ODB exceeded its supported thread count.", + new IllegalStateException("Maximum threads: " + MAX_THREADS)); + } System.exit(1); return(-1); } diff --git a/src/main/java/com/lambda/Debugger/VectorD.java b/src/main/java/com/lambda/Debugger/VectorD.java index 29b78a9..5c841b6 100644 --- a/src/main/java/com/lambda/Debugger/VectorD.java +++ b/src/main/java/com/lambda/Debugger/VectorD.java @@ -1,1026 +1,177 @@ -/* VectorD.java - - Copyright 2003, Bil Lewis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* - -Use a duplicate of Sun's Vector class so that the ODB can load -an instrumented version of Vector &c without blowing up. - -And it's not synchronized. And it's final. (Faster) - -But some of the Java classes appear to use Vectors. :-( - - - * @(#)VectorD.java 1.71 00/04/18 + /* + * Copyright 2003, Bil Lewis * - * Copyright 1994-2000 Sun Microsystems, Inc. All Rights Reserved. - * - * This software is the proprietary information of Sun Microsystems, Inc. - * Use is subject to license terms. - * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any later + * version. */ - -//package java.util; package com.lambda.Debugger; -import java.util.*; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.util.Collection; +import java.util.Vector; /** - * The VectorD class implements a growable array of - * objects. Like an array, it contains components that can be - * accessed using an integer index. However, the size of a - * VectorD can grow or shrink as needed to accommodate - * adding and removing items after the VectorD has been created.

- * - * Each vector tries to optimize storage management by maintaining a - * capacity and a capacityIncrement. The - * capacity is always at least as large as the vector - * size; it is usually larger because as components are added to the - * vector, the vector's storage increases in chunks the size of - * capacityIncrement. An application can increase the - * capacity of a vector before inserting a large number of - * components; this reduces the amount of incremental reallocation.

- * - * As of the Java 2 platform v1.2, this class has been retrofitted to - * implement List, so that it becomes a part of Java's collection framework. - * Unlike the new collection implementations, VectorD is .

+ * ODB vector compatibility type. * - * The Iterators returned by VectorD's iterator and listIterator - * methods are fail-fast: if the VectorD is structurally modified - * at any time after the Iterator is created, in any way except through the - * Iterator's own remove or add methods, the Iterator will throw a - * ConcurrentModificationException. Thus, in the face of concurrent - * modification, the Iterator fails quickly and cleanly, rather than risking - * arbitrary, non-deterministic behavior at an undetermined time in the future. - * The Enumerations returned by VectorD's elements method are not - * fail-fast. - * - * @author Lee Boynton - * @author Jonathan Payne - * @version 1.71, 04/18/00 - * @see Collection - * @see List - * @see ArrayList - * @see LinkedList - * @since JDK1.0 + * Java's Vector owns storage and synchronization. ODB's historic reference- + * identity search/removal and non-traversing debugger string are retained. */ -//public class VectorD extends MyAbstractList implements List, Cloneable, java.io.Serializable { public final class VectorD extends Vector { - /** - * The array buffer into which the components of the vector are - * stored. The capacity of the vector is the length of this array buffer, - * and is at least large enough to contain all the vector's elements.

- * - * Any array elements following the last element in the VectorD are null. - * - * @serial - */ - protected Object elementData[]; - - /** - * The number of valid components in this VectorD object. - * Components elementData[0] through - * elementData[elementCount-1] are the actual items. - * - * @serial - */ - protected int elementCount; - - /** - * The amount by which the capacity of the vector is automatically - * incremented when its size becomes greater than its capacity. If - * the capacity increment is less than or equal to zero, the capacity - * of the vector is doubled each time it needs to grow. - * - * @serial - */ - protected int capacityIncrement; - - /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -2767605614048989439L; + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("capacityIncrement", Integer.TYPE), + new ObjectStreamField("elementCount", Integer.TYPE), + new ObjectStreamField("id", Integer.TYPE), + new ObjectStreamField("idCounter", Integer.TYPE), + new ObjectStreamField("elementData", Object[].class) + }; + + protected int idCounter = 0; + protected int id = idCounter++; + private transient int legacyCapacityIncrement; - /** - * Constructs an empty vector with the specified initial capacity and - * capacity increment. - * - * @param initialCapacity the initial capacity of the vector. - * @param capacityIncrement the amount by which the capacity is - * increased when the vector overflows. - * @exception IllegalArgumentException if the specified initial capacity - * is negative - */ public VectorD(int initialCapacity, int capacityIncrement) { - super(); - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); - this.elementData = new Object[initialCapacity]; - this.capacityIncrement = capacityIncrement; + super(initialCapacity, capacityIncrement); + legacyCapacityIncrement = capacityIncrement; } - /** - * Constructs an empty vector with the specified initial capacity and - * with its capacity increment equal to zero. - * - * @param initialCapacity the initial capacity of the vector. - * @exception IllegalArgumentException if the specified initial capacity - * is negative - */ public VectorD(int initialCapacity) { - this(initialCapacity, 0); + super(initialCapacity); } - /** - * Constructs an empty vector so that its internal data array - * has size 10 and its standard capacity increment is - * zero. - */ public VectorD() { - this(10); - } - - /** - * Constructs a vector containing the elements of the specified - * collection, in the order they are returned by the collection's - * iterator. - * - * @param c the collection whose elements are to be placed into this - * vector. - * @since 1.2 - */ - public VectorD(Collection c) { - elementCount = c.size(); - elementData = new Object[(elementCount*110)/100]; // 10% for growth - c.toArray(elementData); - } - - /** - * Copies the components of this vector into the specified array. The - * item at index k in this vector is copied into component - * k of anArray. The array must be big enough to hold - * all the objects in this vector, else an - * IndexOutOfBoundsException is thrown. - * - * @param anArray the array into which the components get copied. - */ - public void copyInto(Object anArray[]) { - System.arraycopy(elementData, 0, anArray, 0, elementCount); - } - - /** - * Trims the capacity of this vector to be the vector's current - * size. If the capacity of this cector is larger than its current - * size, then the capacity is changed to equal the size by replacing - * its internal data array, kept in the field elementData, - * with a smaller one. An application can use this operation to - * minimize the storage of a vector. - */ - public void trimToSize() { - modCount++; - int oldCapacity = elementData.length; - if (elementCount < oldCapacity) { - Object oldData[] = elementData; - elementData = new Object[elementCount]; - System.arraycopy(oldData, 0, elementData, 0, elementCount); - } - } - - /** - * Increases the capacity of this vector, if necessary, to ensure - * that it can hold at least the number of components specified by - * the minimum capacity argument. - * - *

If the current capacity of this vector is less than - * minCapacity, then its capacity is increased by replacing its - * internal data array, kept in the field elementData, with a - * larger one. The size of the new data array will be the old size plus - * capacityIncrement, unless the value of - * capacityIncrement is less than or equal to zero, in which case - * the new capacity will be twice the old capacity; but if this new size - * is still smaller than minCapacity, then the new capacity will - * be minCapacity. - * - * @param minCapacity the desired minimum capacity. - */ - public void ensureCapacity(int minCapacity) { - modCount++; - ensureCapacityHelper(minCapacity); - } - - /** - * This implements the un semantics of ensureCapacity. - * methods in this class can internally call this - * method for ensuring capacity without incurring the cost of an - * extra synchronization. - * - * @see java.util.VectorD#ensureCapacity(int) - */ - private void ensureCapacityHelper(int minCapacity) { - int oldCapacity = elementData.length; - if (minCapacity > oldCapacity) { - Object oldData[] = elementData; - int newCapacity = (capacityIncrement > 0) ? - (oldCapacity + capacityIncrement) : (oldCapacity * 4); - if (newCapacity < minCapacity) { - newCapacity = minCapacity; - } - elementData = new Object[newCapacity]; - System.arraycopy(oldData, 0, elementData, 0, elementCount); - } - } - - /** - * Sets the size of this vector. If the new size is greater than the - * current size, new null items are added to the end of - * the vector. If the new size is less than the current size, all - * components at index newSize and greater are discarded. - * - * @param newSize the new size of this vector. - * @throws ArrayIndexOutOfBoundsException if new size is negative. - */ - public void setSize(int newSize) { - modCount++; - if (newSize > elementCount) { - ensureCapacityHelper(newSize); - } else { - for (int i = newSize ; i < elementCount ; i++) { - elementData[i] = null; - } - } - elementCount = newSize; - } - - /** - * Returns the current capacity of this vector. - * - * @return the current capacity (the length of its internal - * data arary, kept in the field elementData - * of this vector. - */ - public int capacity() { - return elementData.length; - } - - /** - * Returns the number of components in this vector. - * - * @return the number of components in this vector. - */ - public int size() { - return elementCount; - } - - /** - * Tests if this vector has no components. - * - * @return true if and only if this vector has - * no components, that is, its size is zero; - * false otherwise. - */ - public boolean isEmpty() { - return elementCount == 0; - } - - /** - * Returns an enumeration of the components of this vector. The - * returned Enumeration object will generate all items in - * this vector. The first item generated is the item at index 0, - * then the item at index 1, and so on. - * - * @return an enumeration of the components of this vector. - * @see Enumeration - * @see Iterator - */ - public Enumeration elements() { - return new Enumeration() { - int count = 0; - - public boolean hasMoreElements() { - return count < elementCount; - } - - public Object nextElement() { - if (count < elementCount) { - return elementData[count++]; - } - throw new NoSuchElementException("VectorD Enumeration"); - } - }; - } - - /** - * Tests if the specified object is a component in this vector. - * - * @param elem an object. - * @return true if and only if the specified object - * is the same as a component in this vector, as determined by the - * equals method; false otherwise. - */ - public boolean contains(Object elem) { - return indexOf(elem, 0) >= 0; - } - - /** - * Searches for the first occurence of the given argument, testing - * for equality using the equals method. - * - * @param elem an object. - * @return the index of the first occurrence of the argument in this - * vector, that is, the smallest value k such that - * elem.equals(elementData[k]) is true; - * returns -1 if the object is not found. - * @see Object#equals(Object) - */ - public int indexOf(Object elem) { - return indexOf(elem, 0); - } - - /** - * Searches for the first occurence of the given argument, beginning - * the search at index, and testing for equality using - * the equals method. - * - * @param elem an object. - * @param index the non-negative index to start searching from. - * @return the index of the first occurrence of the object argument in - * this vector at position index or later in the - * vector, that is, the smallest value k such that - * elem.equals(elementData[k]) and (k >= index) is - * true; returns -1 if the object is not - * found. (Returns -1 if index >= the - * current size of this VectorD.) - * @exception IndexOutOfBoundsException if index is negative. - * @see Object#equals(Object) - */ - public int indexOf(Object elem, int index) { - if (elem == null) { - for (int i = index ; i < elementCount ; i++) - if (elementData[i]==null) - return i; - } else { - for (int i = index ; i < elementCount ; i++) - if (elem == elementData[i]) - return i; - } - return -1; - } - - /** - * Returns the index of the last occurrence of the specified object in - * this vector. - * - * @param elem the desired component. - * @return the index of the last occurrence of the specified object in - * this vector, that is, the largest value k such that - * elem.equals(elementData[k]) is true; - * returns -1 if the object is not found. - */ - public int lastIndexOf(Object elem) { - return lastIndexOf(elem, elementCount-1); - } - - /** - * Searches backwards for the specified object, starting from the - * specified index, and returns an index to it. - * - * @param elem the desired component. - * @param index the index to start searching from. - * @return the index of the last occurrence of the specified object in this - * vector at position less than or equal to index in - * the vector, that is, the largest value k such that - * elem.equals(elementData[k]) and (k <= index) is - * true; -1 if the object is not found. - * (Returns -1 if index is negative.) - * @exception IndexOutOfBoundsException if index is greater - * than or equal to the current size of this vector. - */ - public int lastIndexOf(Object elem, int index) { - if (index >= elementCount) - throw new IndexOutOfBoundsException(index + " >= "+ elementCount); - - if (elem == null) { - for (int i = index; i >= 0; i--) - if (elementData[i]==null) - return i; - } else { - for (int i = index; i >= 0; i--) - if (elem == elementData[i]) - return i; - } - return -1; - } - - /** - * Returns the component at the specified index.

- * - * This method is identical in functionality to the get method - * (which is part of the List interface). - * - * @param index an index into this vector. - * @return the component at the specified index. - * @exception ArrayIndexOutOfBoundsException if the index - * is negative or not less than the current size of this - * VectorD object. - * given. - * @see #get(int) - * @see List - */ - public Object elementAt(int index) { - if (index >= elementCount) { - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - } - /* Since try/catch is free, except when the exception is thrown, - put in this extra try/catch to catch negative indexes and - display a more informative error message. This might not - be appropriate, especially if we have a decent debugging - environment - JP. */ - try { - return elementData[index]; - } catch (ArrayIndexOutOfBoundsException e) { - throw new ArrayIndexOutOfBoundsException(index + " < 0"); - } - } - - /** - * Returns the first component (the item at index 0) of - * this vector. - * - * @return the first component of this vector. - * @exception NoSuchElementException if this vector has no components. - */ - public Object firstElement() { - if (elementCount == 0) { - throw new NoSuchElementException(); - } - return elementData[0]; - } - - /** - * Returns the last component of the vector. - * - * @return the last component of the vector, i.e., the component at index - * size() - 1. - * @exception NoSuchElementException if this vector is empty. - */ - public Object lastElement() { - if (elementCount == 0) { - throw new NoSuchElementException(); - } - return elementData[elementCount - 1]; - } - - /** - * Sets the component at the specified index of this - * vector to be the specified object. The previous component at that - * position is discarded.

- * - * The index must be a value greater than or equal to 0 - * and less than the current size of the vector.

- * - * This method is identical in functionality to the set method - * (which is part of the List interface). Note that the set method reverses - * the order of the parameters, to more closely match array usage. Note - * also that the set method returns the old value that was stored at the - * specified position. - * - * @param obj what the component is to be set to. - * @param index the specified index. - * @exception ArrayIndexOutOfBoundsException if the index was invalid. - * @see #size() - * @see List - * @see #set(int, java.lang.Object) - */ - public void setElementAt(Object obj, int index) { - if (index >= elementCount) { - throw new ArrayIndexOutOfBoundsException(index + " >= " + - elementCount); - } - elementData[index] = obj; - } - - /** - * Deletes the component at the specified index. Each component in - * this vector with an index greater or equal to the specified - * index is shifted downward to have an index one - * smaller than the value it had previously. The size of this vector - * is decreased by 1.

- * - * The index must be a value greater than or equal to 0 - * and less than the current size of the vector.

- * - * This method is identical in functionality to the remove method - * (which is part of the List interface). Note that the remove method - * returns the old value that was stored at the specified position. - * - * @param index the index of the object to remove. - * @exception ArrayIndexOutOfBoundsException if the index was invalid. - * @see #size() - * @see #remove(int) - * @see List - */ - public void removeElementAt(int index) { - modCount++; - if (index >= elementCount) { - throw new ArrayIndexOutOfBoundsException(index + " >= " + - elementCount); - } - else if (index < 0) { - throw new ArrayIndexOutOfBoundsException(index); - } - int j = elementCount - index - 1; - if (j > 0) { - System.arraycopy(elementData, index + 1, elementData, index, j); - } - elementCount--; - elementData[elementCount] = null; /* to let gc do its work */ - } - - /** - * Inserts the specified object as a component in this vector at the - * specified index. Each component in this vector with - * an index greater or equal to the specified index is - * shifted upward to have an index one greater than the value it had - * previously.

- * - * The index must be a value greater than or equal to 0 - * and less than or equal to the current size of the vector. (If the - * index is equal to the current size of the vector, the new element - * is appended to the VectorD.)

- * - * This method is identical in functionality to the add(Object, int) method - * (which is part of the List interface). Note that the add method reverses - * the order of the parameters, to more closely match array usage. - * - * @param obj the component to insert. - * @param index where to insert the new component. - * @exception ArrayIndexOutOfBoundsException if the index was invalid. - * @see #size() - * @see #add(int, Object) - * @see List - */ - public void insertElementAt(Object obj, int index) { - modCount++; - if (index >= elementCount + 1) { - throw new ArrayIndexOutOfBoundsException(index - + " > " + elementCount); - } - ensureCapacityHelper(elementCount + 1); - System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); - elementData[index] = obj; - elementCount++; - } - - /** - * Adds the specified component to the end of this vector, - * increasing its size by one. The capacity of this vector is - * increased if its size becomes greater than its capacity.

- * - * This method is identical in functionality to the add(Object) method - * (which is part of the List interface). - * - * @param obj the component to be added. - * @see #add(Object) - * @see List - */ - public void addElement(Object obj) { - modCount++; - ensureCapacityHelper(elementCount + 1); - elementData[elementCount++] = obj; + super(); } - /** - * Removes the first (lowest-indexed) occurrence of the argument - * from this vector. If the object is found in this vector, each - * component in the vector with an index greater or equal to the - * object's index is shifted downward to have an index one smaller - * than the value it had previously.

- * - * This method is identical in functionality to the remove(Object) - * method (which is part of the List interface). - * - * @param obj the component to be removed. - * @return true if the argument was a component of this - * vector; false otherwise. - * @see List#remove(Object) - * @see List - */ - public boolean removeElement(Object obj) { - modCount++; - int i = indexOf(obj); - if (i >= 0) { - removeElementAt(i); - return true; - } - return false; + public VectorD(Collection collection) { + super(collection); } - /** - * Removes all components from this vector and sets its size to zero.

- * - * This method is identical in functionality to the clear method - * (which is part of the List interface). - * - * @see #clear - * @see List - */ - public void removeAllElements() { - // Let gc do its work - - for (int i = 0; i < elementCount; i++) - elementData[i] = null; - - elementCount = 0; - } - - /** - * Returns a clone of this vector. The copy will contain a - * reference to a clone of the internal data array, not a reference - * to the original internal data array of this VectorD object. - * - * @return a clone of this vector. - public Object clone() { - try { - VectorD v = (VectorD)super.clone(); - v.elementData = new Object[elementCount]; - System.arraycopy(elementData, 0, v.elementData, 0, elementCount); - v.modCount = 0; - return v; - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } + public synchronized boolean contains(Object value) { + return indexOf(value, 0) >= 0; } - */ - /** - * Returns an array containing all of the elements in this VectorD - * in the correct order. - * - * @since 1.2 - */ - public Object[] toArray() { - Object[] result = new Object[elementCount]; - System.arraycopy(elementData, 0, result, 0, elementCount); - return result; + public synchronized int indexOf(Object value) { + return indexOf(value, 0); } - /** - * Returns an array containing all of the elements in this VectorD in the - * correct order. The runtime type of the returned array is that of the - * specified array. If the VectorD fits in the specified array, it is - * returned therein. Otherwise, a new array is allocated with the runtime - * type of the specified array and the size of this VectorD.

- * - * If the VectorD fits in the specified array with room to spare - * (i.e., the array has more elements than the VectorD), - * the element in the array immediately following the end of the - * VectorD is set to null. This is useful in determining the length - * of the VectorD only if the caller knows that the VectorD - * does not contain any null elements. - * - * @param a the array into which the elements of the VectorD are to - * be stored, if it is big enough; otherwise, a new array of the - * same runtime type is allocated for this purpose. - * @return an array containing the elements of the VectorD. - * @exception ArrayStoreException the runtime type of a is not a supertype - * of the runtime type of every element in this VectorD. - */ - public Object[] toArray(Object a[]) { - if (a.length < elementCount) - a = (Object[])java.lang.reflect.Array.newInstance( - a.getClass().getComponentType(), elementCount); - - System.arraycopy(elementData, 0, a, 0, elementCount); - - if (a.length > elementCount) - a[elementCount] = null; - - return a; + public synchronized int indexOf(Object value, int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index); + } + for (int i = index; i < size(); i++) { + if (elementAt(i) == value) { + return i; + } + } + return -1; } - // Positional Access Operations - - /** - * Returns the element at the specified position in this VectorD. - * - * @param index index of element to return. - * @exception ArrayIndexOutOfBoundsException index is out of range (index - * < 0 || index >= size()). - * @since 1.2 - */ - public Object get(int index) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index); - - return elementData[index]; + public synchronized int lastIndexOf(Object value) { + return lastIndexOf(value, size() - 1); } - /** - * Replaces the element at the specified position in this VectorD with the - * specified element. - * - * @param index index of element to replace. - * @param element element to be stored at the specified position. - * @return the element previously at the specified position. - * @exception ArrayIndexOutOfBoundsException index out of range - * (index < 0 || index >= size()). - * @exception IllegalArgumentException fromIndex > toIndex. - * @since 1.2 - */ - public Object set(int index, Object element) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index); - - Object oldValue = elementData[index]; - elementData[index] = element; - return oldValue; + public synchronized int lastIndexOf(Object value, int index) { + if (index >= size()) { + throw new IndexOutOfBoundsException(index + " >= " + size()); + } + for (int i = index; i >= 0; i--) { + if (elementAt(i) == value) { + return i; + } + } + return -1; } - /** - * Appends the specified element to the end of this VectorD. - * - * @param o element to be appended to this VectorD. - * @return true (as per the general contract of Collection.add). - * @since 1.2 - */ - public boolean add(Object o) { - modCount++; - ensureCapacityHelper(elementCount + 1); - elementData[elementCount++] = o; + public synchronized boolean removeElement(Object value) { + int index = indexOf(value); + if (index < 0) { + return false; + } + removeElementAt(index); return true; } - /** - * Removes the first occurrence of the specified element in this VectorD - * If the VectorD does not contain the element, it is unchanged. More - * formally, removes the element with the lowest index i such that - * (o==null ? get(i)==null : o.equals(get(i))) (if such - * an element exists). - * - * @param o element to be removed from this VectorD, if present. - * @return true if the VectorD contained the specified element. - * @since 1.2 - */ - public boolean remove(Object o) { - return removeElement(o); - } - - /** - * Inserts the specified element at the specified position in this VectorD. - * Shifts the element currently at that position (if any) and any - * subsequent elements to the right (adds one to their indices). - * - * @param index index at which the specified element is to be inserted. - * @param element element to be inserted. - * @exception ArrayIndexOutOfBoundsException index is out of range - * (index < 0 || index > size()). - * @since 1.2 - */ - public void add(int index, Object element) { - insertElementAt(element, index); - } - - /** - * Removes the element at the specified position in this VectorD. - * shifts any subsequent elements to the left (subtracts one from their - * indices). Returns the element that was removed from the VectorD. - * - * @exception ArrayIndexOutOfBoundsException index out of range (index - * < 0 || index >= size()). - * @since 1.2 - */ - public Object remove(int index) { - modCount++; - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index); - Object oldValue = elementData[index]; - - int numMoved = elementCount - index - 1; - if (numMoved > 0) - System.arraycopy(elementData, index+1, elementData, index, - numMoved); - elementData[--elementCount] = null; // Let gc do its work - return oldValue; - } - - /** - * Removes all of the elements from this VectorD. The VectorD will - * be empty after this call returns (unless it throws an exception). - * - * @since 1.2 - */ - public void clear() { - removeAllElements(); - } - - // Bulk Operations - - /** - * Returns true if this VectorD contains all of the elements in the - * specified Collection. - * - * @return true if this VectorD contains all of the elements in the - * specified collection. - */ - public boolean containsAll(Collection c) { - return super.containsAll(c); + public synchronized boolean remove(Object value) { + return removeElement(value); } - /** - * Appends all of the elements in the specified Collection to the end of - * this VectorD, in the order that they are returned by the specified - * Collection's Iterator. The behavior of this operation is undefined if - * the specified Collection is modified while the operation is in progress. - * (This implies that the behavior of this call is undefined if the - * specified Collection is this VectorD, and this VectorD is nonempty.) - * - * @param c elements to be inserted into this VectorD. - * @exception ArrayIndexOutOfBoundsException index out of range (index - * < 0 || index > size()). - * @since 1.2 - */ - public boolean addAll(Collection c) { - modCount++; - int numNew = c.size(); - ensureCapacityHelper(elementCount + numNew); - - Iterator e = c.iterator(); - for (int i=0; i elementCount) - throw new ArrayIndexOutOfBoundsException(index); - - int numNew = c.size(); - ensureCapacityHelper(elementCount + numNew); - - int numMoved = elementCount - index; - if (numMoved > 0) - System.arraycopy(elementData, index, elementData, index + numNew, - numMoved); - - Iterator e = c.iterator(); - for (int i=0; iequal. (Two elements e1 and - * e2 are equal if (e1==null ? e2==null : - * e1.equals(e2)).) In other words, two Lists are defined to be - * equal if they contain the same elements in the same order. - * - * @param o the Object to be compared for equality with this VectorD. - * @return true if the specified Object is equal to this VectorD - */ - public boolean equals(Object o) { - return super.equals(o); - } - - /** - * Returns the hash code value for this VectorD. - */ - public int hashCode() { - return super.hashCode(); + return true; } - /** - * Returns a string representation of this VectorD, containing - * the String representation of each element. - */ - - protected int idCounter = 0; - protected int id=idCounter++; - public String toString() { - return ""; + public synchronized boolean removeAll(Collection collection) { + boolean changed = false; + for (int i = size() - 1; i >= 0; i--) { + if (collection.contains(elementAt(i))) { + removeElementAt(i); + changed = true; + } + } + return changed; } - /** - * Returns a view of the portion of this List between fromIndex, - * inclusive, and toIndex, exclusive. (If fromIndex and ToIndex are - * equal, the returned List is empty.) The returned List is backed by this - * List, so changes in the returned List are reflected in this List, and - * vice-versa. The returned List supports all of the optional List - * operations supported by this List.

- * - * This method eliminates the need for explicit range operations (of - * the sort that commonly exist for arrays). Any operation that expects - * a List can be used as a range operation by operating on a subList view - * instead of a whole List. For example, the following idiom - * removes a range of elements from a List: - *

-     *	    list.subList(from, to).clear();
-     * 
- * Similar idioms may be constructed for indexOf and lastIndexOf, - * and all of the algorithms in the Collections class can be applied to - * a subList.

- * - * The semantics of the List returned by this method become undefined if - * the backing list (i.e., this List) is structurally modified in - * any way other than via the returned List. (Structural modifications are - * those that change the size of the List, or otherwise perturb it in such - * a fashion that iterations in progress may yield incorrect results.) - * - * @param fromIndex low endpoint (inclusive) of the subList. - * @param toIndex high endpoint (exclusive) of the subList. - * @return a view of the specified range within this List. - * @throws IndexOutOfBoundsException endpoint index value out of range - * (fromIndex < 0 || toIndex > size) - * @throws IllegalArgumentException endpoint indices out of order - * (fromIndex > toIndex) - */ - /* - public List subList(int fromIndex, int toIndex) { - return Collections.synchronizedList(super.subList(fromIndex, toIndex), - this); + public synchronized boolean retainAll(Collection collection) { + boolean changed = false; + for (int i = size() - 1; i >= 0; i--) { + if (!collection.contains(elementAt(i))) { + removeElementAt(i); + changed = true; + } + } + return changed; } - */ - - /** - * Removes from this List all of the elements whose index is between - * fromIndex, inclusive and toIndex, exclusive. Shifts any succeeding - * elements to the left (reduces their index). - * This call shortens the ArrayList by (toIndex - fromIndex) elements. (If - * toIndex==fromIndex, this operation has no effect.) - * - * @param fromIndex index of first element to be removed. - * @param toIndex index after last element to be removed. - */ - protected void removeRange(int fromIndex, int toIndex) { - modCount++; - int numMoved = elementCount - toIndex; - System.arraycopy(elementData, toIndex, elementData, fromIndex, - numMoved); - // Let gc do its work - int newElementCount = elementCount - (toIndex-fromIndex); - while (elementCount != newElementCount) - elementData[--elementCount] = null; + private boolean containsEqual(Object target) { + for (Object value : this) { + if (target == null ? value == null : target.equals(value)) { + return true; + } + } + return false; + } + + public String toString() { + return ""; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + Object[] elements = new Object[capacity()]; + copyInto(elements); + ObjectOutputStream.PutField fields = stream.putFields(); + fields.put("capacityIncrement", legacyCapacityIncrement); + fields.put("elementCount", size()); + fields.put("id", id); + fields.put("idCounter", idCounter); + fields.put("elementData", elements); + stream.writeFields(); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = stream.readFields(); + legacyCapacityIncrement = fields.get("capacityIncrement", 0); + int elementCount = fields.get("elementCount", 0); + id = fields.get("id", 0); + idCounter = fields.get("idCounter", 0); + Object[] elements = (Object[]) fields.get("elementData", null); + if (elements == null) { + return; + } + clear(); + ensureCapacity(elements.length); + for (int i = 0; i < elementCount; i++) { + add(elements[i]); + } } } diff --git a/src/main/resources/META-INF/third-party/asm/LICENSE.txt b/src/main/resources/META-INF/third-party/asm/LICENSE.txt new file mode 100644 index 0000000..942ea50 --- /dev/null +++ b/src/main/resources/META-INF/third-party/asm/LICENSE.txt @@ -0,0 +1,28 @@ +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2011 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/src/test/java/com/lambda/Debugger/CodePaneClassPathTest.java b/src/test/java/com/lambda/Debugger/CodePaneClassPathTest.java new file mode 100644 index 0000000..d61e720 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/CodePaneClassPathTest.java @@ -0,0 +1,24 @@ +package com.lambda.Debugger; + +import static org.junit.Assert.assertNotNull; + +import org.apache.bcel.Repository; +import org.apache.bcel.util.ClassPath; +import org.apache.bcel.util.MemorySensitiveClassPathRepository; +import org.junit.Test; + +public class CodePaneClassPathTest { + @Test + public void repeatedDependencyLookupKeepsRepositoryClassPathOpen() throws Exception { + org.apache.bcel.util.Repository original = Repository.getRepository(); + ClassPath classPath = new ClassPath(System.getProperty("java.class.path")); + Repository.setRepository(new MemorySensitiveClassPathRepository(classPath)); + try { + assertNotNull(CodePane.lookupClassFile("org.apache.bcel.Repository")); + assertNotNull(CodePane.lookupClassFile("org.apache.bcel.Repository")); + } finally { + Repository.setRepository(original); + classPath.close(); + } + } +} diff --git a/src/test/java/com/lambda/Debugger/CollectionCompatibilityTest.java b/src/test/java/com/lambda/Debugger/CollectionCompatibilityTest.java new file mode 100644 index 0000000..ae564a8 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/CollectionCompatibilityTest.java @@ -0,0 +1,384 @@ +package com.lambda.Debugger; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.swing.JList; + +import org.junit.Test; + +public class CollectionCompatibilityTest { + private static final String LEGACY_VECTOR_STREAM = + "rO0ABXNyABtjb20ubGFtYmRhLkRlYnVnZ2VyLlZlY3RvckTZl31bgDuvAQIABUkA" + + "EWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50SQACaWRJAAlpZENvdW50" + + "ZXJbAAtlbGVtZW50RGF0YXQAE1tMamF2YS9sYW5nL09iamVjdDt4cgAQamF2YS51" + + "dGlsLlZlY3RvctmXfVuAO68BAwADSQARY2FwYWNpdHlJbmNyZW1lbnRJAAxlbGVt" + + "ZW50Q291bnRbAAtlbGVtZW50RGF0YXEAfgABeHAAAAAAAAAAAHVyABNbTGphdmEu" + + "bGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAApwcHBwcHBwcHBweAAAAAAAAAAC" + + "AAAAAAAAAAF1cQB+AAQAAAAKdAABYXBwcHBwcHBwcA=="; + private static final String LEGACY_HASH_MAP_STREAM = + "rO0ABXNyAB1jb20ubGFtYmRhLkRlYnVnZ2VyLkhhc2hNYXBFcQUH2sHDFmDRAwAC" + + "RgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAEt3CAAAAGUAAAACdAAB" + + "a3QAAXZwcHg="; + + @Test + public void hashMapEqUsesReferenceIdentityForKeysAndValues() { + HashMapEq map = new HashMapEq(1); + String firstKey = new String("key"); + String secondKey = new String("key"); + String firstValue = new String("value"); + String secondValue = new String("value"); + + assertNull(map.put(firstKey, firstValue)); + assertNull(map.put(secondKey, secondValue)); + + assertEquals(2, map.size()); + assertSame(firstValue, map.get(firstKey)); + assertSame(secondValue, map.get(secondKey)); + assertNull(map.get(new String("key"))); + assertTrue(map.containsValue(firstValue)); + assertFalse(map.containsValue(new String("value"))); + } + + @Test + public void hashMapEqSupportsNullBackedViewsAndCloneIsolation() { + HashMapEq map = new HashMapEq(); + Object key = new Object(); + Object value = new Object(); + map.put(null, null); + map.put(key, value); + + assertTrue(map.keySet().contains(null)); + assertTrue(map.values().contains(value)); + + Iterator entries = map.entrySet().iterator(); + entries.next(); + entries.remove(); + assertEquals(1, map.size()); + + HashMapEq clone = (HashMapEq) map.clone(); + Object cloneOnly = new Object(); + clone.put(cloneOnly, cloneOnly); + assertEquals(1, map.size()); + assertEquals(2, clone.size()); + assertNull(map.get(cloneOnly)); + } + + @Test + public void hashMapEqRetainsLegacyNanLoadFactorAcceptance() { + HashMapEq map = new HashMapEq(1, Float.NaN); + Object key = new Object(); + map.put(key, key); + assertSame(key, map.get(key)); + } + + @Test(expected = ConcurrentModificationException.class) + public void hashMapEqViewsRemainFailFast() { + HashMapEq map = new HashMapEq(); + map.put(new Object(), new Object()); + Iterator keys = map.keySet().iterator(); + + map.put(new Object(), new Object()); + + keys.next(); + } + + @Test + public void hashMapEqReadsLegacySerializedState() throws Exception { + HashMapEq map = (HashMapEq) readLegacyStream(LEGACY_HASH_MAP_STREAM); + + assertEquals(2, map.size()); + assertTrue(map.containsKey(null)); + assertTrue(map.containsValue(null)); + for (Object key : map.keySet()) { + if (key != null) { + assertEquals("k", key); + assertEquals("v", map.get(key)); + } + } + } + + @Test + public void rewrittenCollectionsRoundTripSerializedState() throws Exception { + Object key = new String("key"); + Object value = new String("value"); + HashMapEq map = new HashMapEq(1, 0.75f); + map.put(key, value); + map.put(null, null); + assertEquals(3, map.capacity()); + assertEquals(0.75f, map.loadFactor(), 0.0f); + + HashMapEq mapCopy = (HashMapEq) roundTrip(map); + assertEquals(2, mapCopy.size()); + assertTrue(mapCopy.containsKey(null)); + assertTrue(mapCopy.containsValue(null)); + + VectorD vector = new VectorD(1, 3); + vector.add("value"); + vector.add(null); + VectorD vectorCopy = (VectorD) roundTrip(vector); + assertEquals(2, vectorCopy.size()); + assertEquals("value", vectorCopy.elementAt(0)); + assertNull(vectorCopy.elementAt(1)); + } + + @Test + public void vectorDPreservesLegacyOperationsAndIdentitySearch() { + VectorD vector = new VectorD(1, 1); + String first = new String("same"); + String equalButDistinct = new String("same"); + + vector.add(first); + vector.insertElementAt(equalButDistinct, 0); + vector.add(1, null); + + assertEquals(3, vector.size()); + assertSame(equalButDistinct, vector.firstElement()); + assertSame(first, vector.lastElement()); + assertEquals(2, vector.indexOf(first)); + assertEquals(0, vector.indexOf(equalButDistinct)); + assertEquals(-1, vector.indexOf(new String("same"))); + assertFalse(vector.contains(new String("same"))); + + vector.setElementAt(first, 1); + assertSame(first, vector.elementAt(1)); + assertTrue(vector.removeElement(equalButDistinct)); + assertEquals(2, vector.size()); + assertSame(first, vector.remove(0)); + assertEquals(1, vector.size()); + vector.removeAllElements(); + assertTrue(vector.isEmpty()); + } + + @Test + public void vectorDEnumerationAndStringDoNotTraverseElementStrings() { + Object explosive = new Object() { + public String toString() { + throw new AssertionError("element toString must not be called"); + } + }; + VectorD vector = new VectorD(Arrays.asList(explosive)); + + Enumeration elements = vector.elements(); + assertTrue(elements.hasMoreElements()); + assertSame(explosive, elements.nextElement()); + assertFalse(elements.hasMoreElements()); + assertEquals("", vector.toString()); + } + + @Test + public void vectorDRepairUnifiesStandardViewsWithLegacyStorage() { + Object first = new Object(); + Object second = new Object(); + VectorD vector = new VectorD(Arrays.asList(first, second)); + + assertArrayEquals(new Object[] {first, second}, vector.toArray()); + assertSame(first, vector.iterator().next()); + assertSame(second, vector.listIterator(1).next()); + assertEquals(Arrays.asList(first, second), vector.subList(0, 2)); + } + + @Test + public void vectorDRepairMakesCloneStorageIndependent() { + Object originalValue = new Object(); + Object replacement = new Object(); + VectorD original = new VectorD(Arrays.asList(originalValue)); + + VectorD clone = (VectorD) original.clone(); + original.setElementAt(replacement, 0); + + assertNotSame(original, clone); + assertSame(originalValue, clone.elementAt(0)); + assertSame(replacement, original.elementAt(0)); + } + + @Test + public void vectorDRepairMakesBulkOperationsUsePopulatedStorage() { + String retained = new String("same"); + VectorD vector = new VectorD(Arrays.asList(retained)); + + assertTrue(vector.containsAll(Arrays.asList(new String("same")))); + assertFalse(vector.remove(new String("same"))); + assertTrue(vector.removeAll(Arrays.asList(new String("same")))); + assertTrue(vector.isEmpty()); + + vector.add(retained); + assertFalse(vector.retainAll(Arrays.asList(new String("same")))); + assertSame(retained, vector.firstElement()); + } + + @Test + public void vectorDReadsLegacySerializedState() throws Exception { + VectorD vector = (VectorD) readLegacyStream(LEGACY_VECTOR_STREAM); + + assertEquals(2, vector.size()); + assertEquals("a", vector.elementAt(0)); + assertNull(vector.elementAt(1)); + } + + @Test + public void retainedAbstractCollectionsProvideStandardOperations() { + MutableCollection collection = new MutableCollection(); + collection.add("a"); + collection.add(null); + assertTrue(collection.contains("a")); + assertArrayEquals(new Object[] {"a", null}, collection.toArray()); + assertTrue(collection.remove(null)); + + MutableList list = new MutableList(); + list.add("b"); + list.add(0, "a"); + list.add("c"); + assertEquals(Arrays.asList("a", "b", "c"), list); + assertEquals(1, list.indexOf("b")); + assertEquals("b", list.set(1, "B")); + assertEquals(Arrays.asList("a", "B", "c"), list.subList(0, 3)); + assertEquals("B", list.remove(1)); + assertEquals(Arrays.asList("a", "c"), list); + } + + @Test + public void retainedCollectionsFacadeMatchesJdkAlgorithmsAndWrappers() { + List values = new ArrayList(Arrays.asList(3, 1, 2)); + MyCollections.sort(values); + assertEquals(Arrays.asList(1, 2, 3), values); + assertEquals(1, MyCollections.binarySearch(values, 2)); + MyCollections.reverse(values); + assertEquals(Arrays.asList(3, 2, 1), values); + + List immutable = MyCollections.unmodifiableList(values); + assertEquals(values, immutable); + assertEquals(Arrays.asList("x", "x"), MyCollections.nCopies(2, "x")); + assertTrue(MyCollections.singleton("x").contains("x")); + + Enumeration enumeration = MyCollections.enumeration(values); + assertEquals(3, enumeration.nextElement()); + assertEquals(2, enumeration.nextElement()); + assertEquals(1, enumeration.nextElement()); + assertFalse(enumeration.hasMoreElements()); + } + + @Test(expected = ConcurrentModificationException.class) + public void retainedAbstractListIteratorDetectsDirectStructuralChange() { + MutableList list = new MutableList(); + list.add("first"); + Iterator iterator = list.iterator(); + + list.add("second"); + + iterator.next(); + } + + @Test(expected = ConcurrentModificationException.class) + public void retainedSubListDetectsParentStructuralChange() { + MutableList list = new MutableList(); + list.add("first"); + List subList = list.subList(0, 1); + + list.add("second"); + + subList.size(); + } + + @Test + public void vectorDBacksRealStackAndSwingListModels() { + Object frame = new Object(); + StackList stack = new StackList(); + stack.displayList.add(frame); + assertEquals(1, stack.getSize()); + assertSame(frame, stack.getElementAt(0)); + + VectorD values = new VectorD(Arrays.asList("first", "second")); + JList list = new JList(values); + assertEquals(2, list.getModel().getSize()); + assertEquals("first", list.getModel().getElementAt(0)); + assertEquals("second", list.getModel().getElementAt(1)); + } + + private static Object readLegacyStream(String encoded) throws Exception { + byte[] bytes = Base64.getDecoder().decode(encoded); + ObjectInputStream input = + new ObjectInputStream(new ByteArrayInputStream(bytes)); + try { + return input.readObject(); + } finally { + input.close(); + } + } + + private static Object roundTrip(Object value) throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream output = new ObjectOutputStream(bytes); + output.writeObject(value); + output.close(); + ObjectInputStream input = + new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); + try { + return input.readObject(); + } finally { + input.close(); + } + } + + private static final class MutableCollection extends MyAbstractCollection { + private final Collection delegate = new ArrayList(); + + public Iterator iterator() { + return delegate.iterator(); + } + + public int size() { + return delegate.size(); + } + + public boolean add(Object value) { + return delegate.add(value); + } + } + + private static final class MutableList extends MyAbstractList { + private final List delegate = new ArrayList(); + + public Object get(int index) { + return delegate.get(index); + } + + public int size() { + return delegate.size(); + } + + public Object set(int index, Object value) { + return delegate.set(index, value); + } + + public void add(int index, Object value) { + delegate.add(index, value); + modCount++; + } + + public Object remove(int index) { + Object removed = delegate.remove(index); + modCount++; + return removed; + } + } +} diff --git a/src/test/java/com/lambda/Debugger/DebugifyingClassLoaderTest.java b/src/test/java/com/lambda/Debugger/DebugifyingClassLoaderTest.java index 123321a..462b33d 100644 --- a/src/test/java/com/lambda/Debugger/DebugifyingClassLoaderTest.java +++ b/src/test/java/com/lambda/Debugger/DebugifyingClassLoaderTest.java @@ -1,9 +1,14 @@ package com.lambda.Debugger; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.lang.reflect.Method; +import java.util.List; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -74,4 +79,31 @@ public void platformVendorClassesLoadThroughParent() throws Exception { assertNotSame(loader, clazz.getClassLoader()); } + + @Test + public void instrumentationFailureKeepsLegacyParentFallback() throws Exception { + DebugifyingClassLoader loader = new DebugifyingClassLoader() { + protected Class findClass(String className, boolean instrument) { + throw new VerifyError("fixture failure"); + } + }; + + Class clazz = loader.loadClass("outside.StrictFallbackTarget"); + + assertSame(getClass().getClassLoader(), clazz.getClassLoader()); + } + + @Test + public void arrayListAllocationUsesOdbRecordingImplementation() throws Exception { + DebugifyingClassLoader loader = new DebugifyingClassLoader(); + + Class target = loader.loadClass("outside.ArrayListRecordingTarget"); + Method mutate = target.getMethod("mutate"); + List values = (List) mutate.invoke(null); + + assertSame(loader, target.getClassLoader()); + assertEquals(MyArrayList.class, values.getClass()); + assertEquals(1, values.size()); + assertEquals("changed", values.get(0)); + } } diff --git a/src/test/java/com/lambda/Debugger/IntegrationHeadlessLauncherHarness.java b/src/test/java/com/lambda/Debugger/IntegrationHeadlessLauncherHarness.java new file mode 100644 index 0000000..293abcd --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationHeadlessLauncherHarness.java @@ -0,0 +1,10 @@ +package com.lambda.Debugger; + +public final class IntegrationHeadlessLauncherHarness { + private IntegrationHeadlessLauncherHarness() { + } + + public static void main(String[] args) { + IntegrationLauncher.run(args, false); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationHistoryFailureHarness.java b/src/test/java/com/lambda/Debugger/IntegrationHistoryFailureHarness.java new file mode 100644 index 0000000..83e2e4c --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationHistoryFailureHarness.java @@ -0,0 +1,17 @@ +package com.lambda.Debugger; + +public final class IntegrationHistoryFailureHarness { + private IntegrationHistoryFailureHarness() { + } + + public static void main(String[] args) throws Exception { + IntegrationState.start(System.err, new String[] { "outside.Target" }); + Debugger.programName = "outside.Target"; + if (args.length > 0 && "read".equals(args[0])) { + DebuggerCommandHistoryList.readHistory(); + } else { + DebuggerCommandHistoryList.writeHistory(); + } + System.exit(99); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationInternalFailureHarness.java b/src/test/java/com/lambda/Debugger/IntegrationInternalFailureHarness.java new file mode 100644 index 0000000..e42d0f1 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationInternalFailureHarness.java @@ -0,0 +1,14 @@ +package com.lambda.Debugger; + +public final class IntegrationInternalFailureHarness { + private IntegrationInternalFailureHarness() { + } + + public static void main(String[] args) throws Exception { + IntegrationState.start(System.err, new String[] { "outside.Target" }); + for (int index = 0; index <= TimeStamp.MAX_THREADS; index++) { + TimeStamp.getThreadIndex(new Thread("integration-thread-" + index)); + } + System.exit(99); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationLauncherProcessTest.java b/src/test/java/com/lambda/Debugger/IntegrationLauncherProcessTest.java new file mode 100644 index 0000000..b2fe2f7 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationLauncherProcessTest.java @@ -0,0 +1,430 @@ +package com.lambda.Debugger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +public class IntegrationLauncherProcessTest { + private static final String TOKEN = "0123456789abcdef0123456789abcdef"; + + @Test + public void missingTargetFailsTheContractBeforeOdbInitialization() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launchMain(state, TOKEN, "com.lambda.Debugger.IntegrationLauncher"); + + assertEquals(2, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("@@ODB-INTEGRATION@@\t" + TOKEN + "\t")); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"fatal\"")); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"BAD_CONTRACT\"")); + assertFalse(result.stderr, result.stderr.contains("Lewis Omniscient Debugger")); + } + + @Test + public void missingTargetClassFailsAfterCreatingOnlyManagedDefaults() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launch(state, TOKEN, "outside.MissingTarget"); + + assertEquals(1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"runtime-ready\"")); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"TARGET_CLASS_NOT_FOUND\"")); + assertTrue(Files.isRegularFile(state.resolve(".debuggerDefaults"))); + assertFalse(Files.exists(result.workingDirectory.resolve(".debuggerDefaults"))); + } + + @Test + public void targetWithoutPublicStaticVoidMainFailsExplicitly() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launch(state, TOKEN, "outside.InvalidMainTarget"); + + assertEquals(1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"MAIN_METHOD_INVALID\"")); + assertFalse(result.stderr, result.stderr.contains("\"type\":\"target-loaded\"")); + } + + @Test + public void targetThatProducesNoRecordingFailsBeforeOpeningDebugger() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launch(state, TOKEN, "outside.NoRecordingTarget"); + + assertEquals(1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"target-loaded\"")); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"NO_RECORDING\"")); + assertFalse(result.stderr, result.stderr.contains("\"type\":\"debugger-ready\"")); + } + + @Test + public void externalTargetKeepsLaunchInputsAndEventsUseOriginalStderr() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launch(state, TOKEN, "outside.BehaviorTarget", "one", "two words", "السلام"); + + assertEquals(23, result.exitCode); + assertTrue(result.stdout, result.stdout.contains("args=[one, two words, السلام]")); + assertTrue(result.stdout, result.stdout.contains("user.dir=" + result.workingDirectory.toRealPath())); + assertTrue(result.stdout, result.stdout.contains("cwd=" + result.workingDirectory.toRealPath())); + assertTrue(result.stdout, result.stdout.contains("classpath-helper=kept")); + assertTrue(result.stdout, result.stdout.contains("state-property=null")); + assertTrue(result.stdout, result.stdout.contains("token-property=null")); + assertTrue(Files.isRegularFile(result.workingDirectory.resolve("target-relative.txt"))); + assertTrue(Files.isRegularFile(result.workingDirectory.resolve("redirected-stderr.txt"))); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"runtime-ready\"")); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"target-loaded\"")); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"recording-started\"")); + assertTrue(Files.isRegularFile(state.resolve(".debuggerDefaults"))); + } + + @Test + public void eagerAndLazyInstrumentationFailuresAreFatal() throws Exception { + for (String mode : Arrays.asList("eager", "lazy")) { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launchMain( + state, + TOKEN, + "com.lambda.Debugger.IntegrationStrictLoaderHarness", + mode); + + assertEquals(mode + "\n" + result.stderr, 1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"INSTRUMENTATION_FAILED\"")); + assertTrue(result.stderr, result.stderr.contains("\"class\":\"outside.StrictFallbackTarget\"")); + } + } + + @Test + public void invalidStateTokenTargetAndForbiddenFlagsFailTheContract() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + Path stateFile = Files.createTempFile("odb-integration-state-file", ".tmp"); + Path missingState = state.resolve("missing-state"); + Path unwritableState = Files.createTempDirectory("odb-integration-unwritable"); + assertTrue("could not make state fixture non-writable", unwritableState.toFile().setWritable(false, false)); + assertFalse("state fixture remained writable", Files.isWritable(unwritableState)); + Result unwritableResult; + try { + unwritableResult = launchRaw( + unwritableState.toString(), + TOKEN, + new String[0], + "outside.MissingTarget"); + } finally { + assertTrue("could not restore state fixture", unwritableState.toFile().setWritable(true, false)); + } + List results = new ArrayList(Arrays.asList( + launchRaw(null, TOKEN, new String[0], "outside.MissingTarget"), + launchRaw("relative-state", TOKEN, new String[0], "outside.MissingTarget"), + launchRaw(missingState.toString(), TOKEN, new String[0], "outside.MissingTarget"), + launchRaw(stateFile.toString(), TOKEN, new String[0], "outside.MissingTarget"), + unwritableResult, + launchRaw(state.toString(), "ABCDEF", new String[0], "outside.MissingTarget"), + launchRaw(state.toString(), "bad\nسلام", new String[0], "outside.MissingTarget"), + launchRaw(state.toString(), TOKEN, new String[0], "../Target"))); + for (String flag : Arrays.asList( + "DONT_INSTRUMENT", + "DONT_START", + "PAUSED", + "DONT_SHOW", + "NO_WINDOWS", + "NO_DEFAULTS", + "DEBUGIFY_ONLY", + "DONT_KILL_TARGET")) { + results.add(launchRaw( + state.toString(), + TOKEN, + new String[] { "-D" + flag + "=true" }, + "outside.MissingTarget")); + } + + for (Result result : results) { + assertEquals(result.stderr, 2, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"BAD_CONTRACT\"")); + assertFalse(result.stderr, result.stderr.contains("\"type\":\"runtime-ready\"")); + } + Result unsafeToken = results.get(6); + assertFalse(unsafeToken.stderr, unsafeToken.stderr.contains("سلام")); + assertEquals(1, occurrences(unsafeToken.stderr, "\n")); + } + + @Test + public void defaultsAndCommandHistoryResolveUnderManagedState() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launchMain( + state, + TOKEN, + "com.lambda.Debugger.IntegrationManagedPathHarness"); + + assertEquals(result.stderr, 0, result.exitCode); + assertTrue(result.stdout, result.stdout.contains("defaults=" + state.toRealPath().resolve(".debuggerDefaults"))); + assertTrue( + result.stdout, + result.stdout.contains("history=" + state.toRealPath().resolve("outside.Target.debuggerCommands"))); + assertFalse(Files.exists(result.workingDirectory.resolve(".debuggerDefaults"))); + assertFalse(Files.exists(result.workingDirectory.resolve("outside.Target.debuggerCommands"))); + } + + @Test + public void integrationSourceRootsLoadSourceWithoutOpeningTheChooser() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + Path sourceRoot = Files.createTempDirectory("odb source root ").toRealPath(); + Path sourceFile = sourceRoot.resolve("outside/BehaviorTarget.java"); + Files.createDirectories(sourceFile.getParent()); + Files.copy( + new File("src/test/java/outside/BehaviorTarget.java").toPath(), + sourceFile); + Files.write( + state.resolve("source-roots.txt"), + Arrays.asList(sourceRoot.toString()), + StandardCharsets.UTF_8); + + Result result = launchMain( + state, + TOKEN, + "com.lambda.Debugger.IntegrationSourceLookupHarness"); + + assertEquals(result.stderr, 0, result.exitCode); + assertTrue(result.stdout, result.stdout.contains("source-lines=")); + assertTrue(result.stdout, result.stdout.contains("default-dont-record=5")); + assertFalse(result.stderr, result.stderr.contains("HeadlessException")); + } + + @Test + public void defaultsIoAndInternalInitializationFailuresAreExplicit() throws Exception { + Path badDefaultsState = Files.createTempDirectory("odb-integration-state"); + Files.createDirectory(badDefaultsState.resolve(".debuggerDefaults")); + + Result defaultsFailure = launch(badDefaultsState, TOKEN, "outside.MissingTarget"); + Result internalFailure = launchRaw( + Files.createTempDirectory("odb-integration-state").toString(), + TOKEN, + new String[] { "-DMEMORY=not-a-number" }, + "outside.MissingTarget"); + + assertEquals(defaultsFailure.stderr, 1, defaultsFailure.exitCode); + assertTrue(defaultsFailure.stderr, defaultsFailure.stderr.contains("\"code\":\"DEFAULTS_IO\"")); + assertEquals(internalFailure.stderr, 1, internalFailure.exitCode); + assertTrue(internalFailure.stderr, internalFailure.stderr.contains("\"code\":\"INTERNAL_ERROR\"")); + } + + @Test + public void legacyMainKeepsHumanFailureAndWorkingDirectoryDefaults() throws Exception { + Result result = launchProcess( + null, + null, + new String[0], + "com.lambda.Debugger.Debugger", + "outside.LegacyMissingTarget"); + + assertEquals(1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("Class not found: outside.LegacyMissingTarget")); + assertFalse(result.stderr, result.stderr.contains("@@ODB-INTEGRATION@@")); + assertTrue(Files.isRegularFile(result.workingDirectory.resolve(".debuggerDefaults"))); + } + + @Test + public void protocolIsAsciiOrderedAndUsesCapturedOriginalStderr() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launchMain( + state, + TOKEN, + "com.lambda.Debugger.IntegrationProtocolHarness"); + + assertEquals(result.stderr, 0, result.exitCode); + assertFalse(result.stderr, result.stderr.contains("سلام")); + assertTrue(result.stderr, result.stderr.contains("\\u0633\\u0644\\u0627\\u0645")); + assertOrdered(result.stderr, "\"sequence\":1", "\"sequence\":2", "\"sequence\":3", "\"sequence\":4"); + assertEquals(1, occurrences(result.stderr, "\"type\":\"recording-started\"")); + assertTrue(result.stderr, result.stderr.contains("\"type\":\"debugger-ready\"")); + for (int index = 0; index < result.stderr.length(); index++) { + assertTrue(result.stderr, result.stderr.charAt(index) <= 0x7f); + } + } + + @Test + public void primaryTargetExcludedFromInstrumentationNeverRunsUninstrumented() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + Files.write( + state.resolve(".debuggerDefaults"), + "OnlyInstrument: \"some.other.package.\"\n".getBytes(StandardCharsets.UTF_8)); + + Result result = launch(state, TOKEN, "outside.BehaviorTarget", "must-not-run"); + + assertEquals(result.stderr, 1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"INSTRUMENTATION_FAILED\"")); + assertFalse(result.stderr, result.stderr.contains("\"type\":\"target-loaded\"")); + assertFalse(Files.exists(result.workingDirectory.resolve("target-relative.txt"))); + } + + @Test + public void commandHistoryIoFailureIsFatalInIntegrationMode() throws Exception { + Path writeState = Files.createTempDirectory("odb-integration-state"); + Files.createDirectory(writeState.resolve("outside.Target.debuggerCommands")); + Path readState = Files.createTempDirectory("odb-integration-state"); + Files.createDirectory(readState.resolve("outside.Target.debuggerCommands")); + + Result writeResult = launchMain( + writeState, + TOKEN, + "com.lambda.Debugger.IntegrationHistoryFailureHarness"); + Result readResult = launchMain( + readState, + TOKEN, + "com.lambda.Debugger.IntegrationHistoryFailureHarness", + "read"); + + for (Result result : Arrays.asList(writeResult, readResult)) { + assertEquals(result.stderr, 1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"DEFAULTS_IO\"")); + } + } + + @Test + public void managedWritesDoNotFollowSymlinksOutsideState() throws Exception { + Path outside = Files.createTempFile("odb-integration-outside", ".txt"); + Files.write(outside, "unchanged".getBytes(StandardCharsets.UTF_8)); + Path defaultsState = Files.createTempDirectory("odb-integration-state"); + Files.createSymbolicLink(defaultsState.resolve(".debuggerDefaults"), outside); + Path historyState = Files.createTempDirectory("odb-integration-state"); + Files.createSymbolicLink(historyState.resolve("outside.Target.debuggerCommands"), outside); + + Result defaultsResult = launch(defaultsState, TOKEN, "outside.MissingTarget"); + Result historyResult = launchMain( + historyState, + TOKEN, + "com.lambda.Debugger.IntegrationHistoryFailureHarness"); + + for (Result result : Arrays.asList(defaultsResult, historyResult)) { + assertEquals(result.stderr, 1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"DEFAULTS_IO\"")); + } + assertEquals("unchanged", new String(Files.readAllBytes(outside), StandardCharsets.UTF_8)); + } + + @Test + public void directInternalFailureEmitsFatalBeforeExit() throws Exception { + Path state = Files.createTempDirectory("odb-integration-state"); + + Result result = launchMain( + state, + TOKEN, + "com.lambda.Debugger.IntegrationInternalFailureHarness"); + + assertEquals(result.stderr, 1, result.exitCode); + assertTrue(result.stderr, result.stderr.contains("\"code\":\"INTERNAL_ERROR\"")); + } + + private Result launch(Path state, String token, String... arguments) throws Exception { + return launchMain(state, token, "com.lambda.Debugger.IntegrationHeadlessLauncherHarness", arguments); + } + + private Result launchMain(Path state, String token, String mainClass, String... arguments) throws Exception { + return launchProcess(state.toString(), token, new String[0], mainClass, arguments); + } + + private Result launchRaw( + String state, + String token, + String[] vmOptions, + String... arguments) throws Exception { + return launchProcess(state, token, vmOptions, "com.lambda.Debugger.IntegrationLauncher", arguments); + } + + private Result launchProcess( + String state, + String token, + String[] vmOptions, + String mainClass, + String... arguments) throws Exception { + List command = new ArrayList(); + command.add(javaExecutable()); + if (state != null) { + command.add("-Dcom.lambda.Debugger.integration.stateDir=" + state); + } + if (token != null) { + command.add("-Dcom.lambda.Debugger.integration.token=" + token); + } + command.addAll(Arrays.asList(vmOptions)); + command.add("-Djava.awt.headless=true"); + command.add("-cp"); + command.add(System.getProperty("java.class.path")); + command.add(mainClass); + command.addAll(Arrays.asList(arguments)); + Path workingDirectory = Files.createTempDirectory("odb-integration-work"); + Process process = new ProcessBuilder(command).directory(workingDirectory.toFile()).start(); + if (!process.waitFor(10, TimeUnit.SECONDS)) { + process.destroyForcibly(); + process.waitFor(5, TimeUnit.SECONDS); + fail("integration process timed out"); + } + return new Result( + process.exitValue(), + read(process.getInputStream()), + read(process.getErrorStream()), + workingDirectory); + } + + private static String javaExecutable() { + return new File(new File(System.getProperty("java.home"), "bin"), "java").getAbsolutePath(); + } + + private static String read(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int count; + while ((count = input.read(buffer)) >= 0) { + output.write(buffer, 0, count); + } + return new String(output.toByteArray(), StandardCharsets.UTF_8); + } + + private static void assertOrdered(String value, String... markers) { + int previous = -1; + for (String marker : markers) { + int current = value.indexOf(marker); + assertTrue(value, current > previous); + previous = current; + } + } + + private static int occurrences(String value, String marker) { + int count = 0; + int offset = 0; + while ((offset = value.indexOf(marker, offset)) >= 0) { + count++; + offset += marker.length(); + } + return count; + } + + private static final class Result { + final int exitCode; + final String stdout; + final String stderr; + final Path workingDirectory; + + Result(int exitCode, String stdout, String stderr, Path workingDirectory) { + this.exitCode = exitCode; + this.stdout = stdout; + this.stderr = stderr; + this.workingDirectory = workingDirectory; + } + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationManagedPathHarness.java b/src/test/java/com/lambda/Debugger/IntegrationManagedPathHarness.java new file mode 100644 index 0000000..fb26a3a --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationManagedPathHarness.java @@ -0,0 +1,12 @@ +package com.lambda.Debugger; + +public final class IntegrationManagedPathHarness { + private IntegrationManagedPathHarness() { + } + + public static void main(String[] args) throws Exception { + IntegrationState.start(System.err, new String[] { "outside.Target" }); + System.out.println("defaults=" + IntegrationState.defaultsFile(".debuggerDefaults")); + System.out.println("history=" + IntegrationState.commandHistoryFile("outside.Target")); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationProtocolHarness.java b/src/test/java/com/lambda/Debugger/IntegrationProtocolHarness.java new file mode 100644 index 0000000..cb1ad97 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationProtocolHarness.java @@ -0,0 +1,18 @@ +package com.lambda.Debugger; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public final class IntegrationProtocolHarness { + private IntegrationProtocolHarness() { + } + + public static void main(String[] args) throws Exception { + IntegrationState.start(System.err, new String[] { "outside.سلام" }); + System.setErr(new PrintStream(new ByteArrayOutputStream(), true, "UTF-8")); + IntegrationState.targetLoaded("outside.سلام"); + IntegrationState.timestampAdded(3, 2); + IntegrationState.timestampAdded(4, 3); + IntegrationState.debuggerReady(4, 3); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationSourceLookupHarness.java b/src/test/java/com/lambda/Debugger/IntegrationSourceLookupHarness.java new file mode 100644 index 0000000..f413ca5 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationSourceLookupHarness.java @@ -0,0 +1,18 @@ +package com.lambda.Debugger; + +public final class IntegrationSourceLookupHarness { + private IntegrationSourceLookupHarness() { + } + + public static void main(String[] args) throws Exception { + IntegrationState.start(System.err, new String[] { "outside.BehaviorTarget" }); + Defaults.readDefaults(); + IntegrationState.loadSourceDirectories(); + VectorD lines = CodePane.getDisplayList("BehaviorTarget.java", "outside.BehaviorTarget"); + if (lines.size() == 0) { + throw new AssertionError("Integration source root did not load BehaviorTarget.java."); + } + System.out.println("source-lines=" + lines.size()); + System.out.println("default-dont-record=" + Defaults.dontRecord.size()); + } +} diff --git a/src/test/java/com/lambda/Debugger/IntegrationStrictLoaderHarness.java b/src/test/java/com/lambda/Debugger/IntegrationStrictLoaderHarness.java new file mode 100644 index 0000000..9deda17 --- /dev/null +++ b/src/test/java/com/lambda/Debugger/IntegrationStrictLoaderHarness.java @@ -0,0 +1,31 @@ +package com.lambda.Debugger; + +public final class IntegrationStrictLoaderHarness { + private IntegrationStrictLoaderHarness() { + } + + public static void main(String[] args) throws Exception { + final DebugifyingClassLoader loader = new DebugifyingClassLoader() { + protected Class findClass(String className, boolean instrument) { + throw new VerifyError("fixture instrumentation failure"); + } + }; + IntegrationState.start(System.err, new String[] { "outside.StrictFallbackTarget" }); + if ("lazy".equals(args[0])) { + Thread thread = new Thread(new Runnable() { + public void run() { + try { + loader.loadClass("outside.StrictFallbackTarget"); + } catch (ClassNotFoundException error) { + throw new AssertionError(error); + } + } + }, "lazy-instrumentation-fixture"); + thread.start(); + thread.join(); + } else { + loader.loadClass("outside.StrictFallbackTarget"); + } + System.exit(99); + } +} diff --git a/src/test/java/com/lambda/Debugger/VerifyRecording.java b/src/test/java/com/lambda/Debugger/VerifyRecording.java index 3b435ae..d0564d2 100644 --- a/src/test/java/com/lambda/Debugger/VerifyRecording.java +++ b/src/test/java/com/lambda/Debugger/VerifyRecording.java @@ -29,7 +29,17 @@ static void verifyRecording(String[] args) { + " timestamps for " + args[0] + " but recorded " + TimeStamp.nTSCreated); } + verifyTraceCollectionViews(); + for (int i = 2; i < args.length; i++) { + if ("@arraylist-history".equals(args[i])) { + if (!hasArrayListMutationHistory()) { + throw new IllegalStateException( + "Missing MyArrayList mutation history"); + } + verifyShadowIdentityTableAndTimelineSwap(); + continue; + } String[] expected = args[i].split("#", 2); if (expected.length != 2) { throw new IllegalArgumentException("Expected trace as class#method: " + args[i]); @@ -40,6 +50,141 @@ static void verifyRecording(String[] args) { } } + private static boolean hasArrayListMutationHistory() { + java.util.Iterator shadows = Shadow.getIterator(); + while (shadows.hasNext()) { + Shadow shadow = (Shadow) shadows.next(); + if (!(shadow.obj instanceof MyArrayList)) { + continue; + } + java.util.Set times = new java.util.HashSet(); + boolean sawFirst = false; + boolean sawZeroth = false; + boolean sawChanged = false; + boolean cleared = true; + for (int i = 0; i < shadow.size(); i++) { + HistoryList history = shadow.getShadowVar(i); + if (history == null) { + continue; + } + for (int j = 0; j < history.size(); j++) { + Object value = history.getValue(j); + sawFirst |= "first".equals(value); + sawZeroth |= "zeroth".equals(value); + sawChanged |= "changed".equals(value); + times.add(Integer.valueOf(history.getTime(j))); + } + cleared &= history.getLastValue() == Dashes.DASHES; + } + if (sawFirst && sawZeroth && sawChanged && cleared + && times.size() >= 5) { + return true; + } + } + return false; + } + + private static void verifyTraceCollectionViews() { + for (int i = 0; i < TraceLine.unfilteredTraceSets.length; i++) { + VectorD traceSet = TraceLine.unfilteredTraceSets[i]; + if (traceSet == null) { + continue; + } + java.util.Iterator iterator = traceSet.iterator(); + java.util.Enumeration enumeration = traceSet.elements(); + for (int j = 0; j < traceSet.size(); j++) { + Object indexed = traceSet.elementAt(j); + if (!iterator.hasNext() || iterator.next() != indexed) { + throw new IllegalStateException( + "Trace iterator differs from indexed storage"); + } + if (!enumeration.hasMoreElements() + || enumeration.nextElement() != indexed) { + throw new IllegalStateException( + "Trace enumeration differs from indexed storage"); + } + } + if (iterator.hasNext() || enumeration.hasMoreElements()) { + throw new IllegalStateException( + "Trace collection view has extra entries"); + } + } + } + + private static void verifyShadowIdentityTableAndTimelineSwap() { + HashMapEq original = Shadow.getTable(); + if (original.isEmpty()) { + throw new IllegalStateException("Shadow identity table is empty"); + } + java.util.Map.Entry sample = (java.util.Map.Entry) + original.entrySet().iterator().next(); + if (original.get(sample.getKey()) != sample.getValue()) { + throw new IllegalStateException("Shadow identity lookup changed"); + } + + EqualObject firstEqual = new EqualObject(); + EqualObject secondEqual = new EqualObject(); + Shadow firstEqualShadow = Shadow.get(firstEqual); + Shadow secondEqualShadow = Shadow.get(secondEqual); + if (firstEqualShadow == secondEqualShadow + || original.get(firstEqual) != firstEqualShadow + || original.get(secondEqual) != secondEqualShadow) { + throw new IllegalStateException( + "Shadow collapsed equal-but-distinct identity keys"); + } + int equalKeysSeen = 0; + java.util.Iterator keys = original.keySet().iterator(); + while (keys.hasNext()) { + Object key = keys.next(); + if (key == firstEqual || key == secondEqual) { + equalKeysSeen++; + } + } + if (equalKeysSeen != 2) { + throw new IllegalStateException( + "Shadow identity keys did not survive table iteration"); + } + + Thread thread = Thread.currentThread(); + Object lock = new Object(); + Shadow threadShadow = Shadow.get(thread); + Shadow lockShadow = Shadow.get(lock); + int time = TimeStamp.nTSCreated; + threadShadow.threadGetting(time, lock, null); + lockShadow.addSleeper(time, thread, null); + if (Shadow.getBlockedHL(thread) == null + || lockShadow.getSleeperSet() == null) { + throw new IllegalStateException( + "Shadow blocked/sleeper identity tables lost entries"); + } + + Shadow.switchTimeLines(false); + HashMapEq alternate = Shadow.getTable(); + Shadow alternateThread = (Shadow) alternate.get(thread); + Shadow alternateLock = (Shadow) alternate.get(lock); + if (alternate == original || alternate.get(sample.getKey()) == null + || alternate.get(firstEqual) == null + || alternate.get(secondEqual) == null + || alternate.get(firstEqual) == alternate.get(secondEqual) + || alternateThread == null + || alternateThread.getBlockedHL() == null + || alternateLock == null + || alternateLock.getSleeperSet() == null) { + throw new IllegalStateException("Shadow timeline table did not swap"); + } + Shadow.switchTimeLines(false); + } + + private static final class EqualObject { + public boolean equals(Object other) { + return other instanceof EqualObject; + } + + public int hashCode() { + return 1; + } + } + private static boolean hasTrace(String className, String methodName) { for (int i = 0; i < TraceLine.unfilteredTraceSets.length; i++) { VectorD traceSet = TraceLine.unfilteredTraceSets[i]; diff --git a/src/test/java/com/lambda/Debugger/VerifyRecordingTest.java b/src/test/java/com/lambda/Debugger/VerifyRecordingTest.java index 4166bc4..d0bfec5 100644 --- a/src/test/java/com/lambda/Debugger/VerifyRecordingTest.java +++ b/src/test/java/com/lambda/Debugger/VerifyRecordingTest.java @@ -1,11 +1,14 @@ package com.lambda.Debugger; import org.junit.Test; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class VerifyRecordingTest { @Test - public void demoRecordsMain() { + public void aDemoRecordsMain() { VerifyRecording.verifyRecording(new String[] { "com.lambda.Debugger.Demo", "1200", @@ -14,11 +17,21 @@ public void demoRecordsMain() { } @Test - public void quickSortRecordsSortNElements() { + public void bQuickSortRecordsSortNElements() { VerifyRecording.verifyRecording(new String[] { "com.lambda.Debugger.QuickSortNonThreaded", "300", "com.lambda.Debugger.QuickSortNonThreaded#sortNElements", }); } + + @Test + public void zArrayListMutationsReachShadowHistory() { + VerifyRecording.verifyRecording(new String[] { + "outside.ArrayListHistoryTarget", + "5", + "outside.ArrayListHistoryTarget#main", + "@arraylist-history", + }); + } } diff --git a/src/test/java/outside/ArrayListHistoryTarget.java b/src/test/java/outside/ArrayListHistoryTarget.java new file mode 100644 index 0000000..581531c --- /dev/null +++ b/src/test/java/outside/ArrayListHistoryTarget.java @@ -0,0 +1,17 @@ +package outside; + +import java.util.ArrayList; + +public final class ArrayListHistoryTarget { + private ArrayListHistoryTarget() { + } + + public static void main(String[] args) { + ArrayList values = new ArrayList(); + values.add("first"); + values.add(0, "zeroth"); + values.set(1, "changed"); + values.remove(0); + values.clear(); + } +} diff --git a/src/test/java/outside/ArrayListRecordingTarget.java b/src/test/java/outside/ArrayListRecordingTarget.java new file mode 100644 index 0000000..5ccb080 --- /dev/null +++ b/src/test/java/outside/ArrayListRecordingTarget.java @@ -0,0 +1,18 @@ +package outside; + +import java.util.ArrayList; +import java.util.List; + +public final class ArrayListRecordingTarget { + private ArrayListRecordingTarget() { + } + + public static List mutate() { + ArrayList values = new ArrayList(); + values.add("first"); + values.add(0, "zeroth"); + values.set(1, "changed"); + values.remove(0); + return values; + } +} diff --git a/src/test/java/outside/BehaviorTarget.java b/src/test/java/outside/BehaviorTarget.java new file mode 100644 index 0000000..d2ba8d9 --- /dev/null +++ b/src/test/java/outside/BehaviorTarget.java @@ -0,0 +1,23 @@ +package outside; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; + +public final class BehaviorTarget { + public static void main(String[] args) throws Exception { + System.setErr(new PrintStream(new FileOutputStream("redirected-stderr.txt"), true, "UTF-8")); + System.out.println("args=" + Arrays.toString(args)); + System.out.println("user.dir=" + System.getProperty("user.dir")); + System.out.println("cwd=" + new File(".").getCanonicalPath()); + System.out.println("classpath-helper=" + TargetClasspathHelper.value()); + System.out.println("state-property=" + System.getProperty("com.lambda.Debugger.integration.stateDir")); + System.out.println("token-property=" + System.getProperty("com.lambda.Debugger.integration.token")); + Files.write(Paths.get("target-relative.txt"), "kept".getBytes(StandardCharsets.UTF_8)); + Runtime.getRuntime().halt(23); + } +} diff --git a/src/test/java/outside/InvalidMainTarget.java b/src/test/java/outside/InvalidMainTarget.java new file mode 100644 index 0000000..304d9f9 --- /dev/null +++ b/src/test/java/outside/InvalidMainTarget.java @@ -0,0 +1,6 @@ +package outside; + +public final class InvalidMainTarget { + public void main(String[] args) { + } +} diff --git a/src/test/java/outside/NoRecordingTarget.java b/src/test/java/outside/NoRecordingTarget.java new file mode 100644 index 0000000..9d9127e --- /dev/null +++ b/src/test/java/outside/NoRecordingTarget.java @@ -0,0 +1,5 @@ +package outside; + +public final class NoRecordingTarget { + public static native void main(String[] args); +} diff --git a/src/test/java/outside/StrictFallbackTarget.java b/src/test/java/outside/StrictFallbackTarget.java new file mode 100644 index 0000000..5119218 --- /dev/null +++ b/src/test/java/outside/StrictFallbackTarget.java @@ -0,0 +1,4 @@ +package outside; + +public final class StrictFallbackTarget { +} diff --git a/src/test/java/outside/TargetClasspathHelper.java b/src/test/java/outside/TargetClasspathHelper.java new file mode 100644 index 0000000..4740ed9 --- /dev/null +++ b/src/test/java/outside/TargetClasspathHelper.java @@ -0,0 +1,10 @@ +package outside; + +public final class TargetClasspathHelper { + private TargetClasspathHelper() { + } + + public static String value() { + return "kept"; + } +} diff --git a/verification/odb-runtime-osv.json b/verification/odb-runtime-osv.json new file mode 100644 index 0000000..f8d4514 --- /dev/null +++ b/verification/odb-runtime-osv.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 1, + "scanner": { + "name": "OSV API", + "endpoint": "https://api.osv.dev/v1/querybatch", + "queriedOn": "2026-07-31" + }, + "packages": [ + { + "purl": "pkg:maven/commons-io/commons-io@2.21.0", + "vulnerabilities": [] + }, + { + "purl": "pkg:maven/org.apache.bcel/bcel@6.12.0", + "vulnerabilities": [] + }, + { + "purl": "pkg:maven/org.apache.commons/commons-lang3@3.20.0", + "vulnerabilities": [] + }, + { + "purl": "pkg:maven/org.ow2.asm/asm@9.7.1", + "vulnerabilities": [] + } + ] +}