|
| 1 | +/* |
| 2 | + * Copyright (c) 2014 Contributor. All rights reserved. |
| 3 | + */ |
| 4 | +package dotty.tools.dotc.classpath |
| 5 | + |
| 6 | +import java.net.URL |
| 7 | +import scala.annotation.tailrec |
| 8 | +import scala.collection.mutable.ArrayBuffer |
| 9 | +import scala.reflect.internal.FatalError |
| 10 | +import scala.reflect.io.AbstractFile |
| 11 | +import dotty.tools.io.ClassPath |
| 12 | +import dotty.tools.io.ClassRepresentation |
| 13 | + |
| 14 | +/** |
| 15 | + * A classpath unifying multiple class- and sourcepath entries. |
| 16 | + * The Classpath can obtain entries for classes and sources independently |
| 17 | + * so it tries to do operations quite optimally - iterating only these collections |
| 18 | + * which are needed in the given moment and only as far as it's necessary. |
| 19 | + * |
| 20 | + * @param aggregates classpath instances containing entries which this class processes |
| 21 | + */ |
| 22 | +case class AggregateClassPath(aggregates: Seq[ClassPath]) extends ClassPath { |
| 23 | + override def findClassFile(className: String): Option[AbstractFile] = { |
| 24 | + @tailrec |
| 25 | + def find(aggregates: Seq[ClassPath]): Option[AbstractFile] = |
| 26 | + if (aggregates.nonEmpty) { |
| 27 | + val classFile = aggregates.head.findClassFile(className) |
| 28 | + if (classFile.isDefined) classFile |
| 29 | + else find(aggregates.tail) |
| 30 | + } else None |
| 31 | + |
| 32 | + find(aggregates) |
| 33 | + } |
| 34 | + |
| 35 | + override def findClass(className: String): Option[ClassRepresentation] = { |
| 36 | + @tailrec |
| 37 | + def findEntry(aggregates: Seq[ClassPath], isSource: Boolean): Option[ClassRepresentation] = |
| 38 | + if (aggregates.nonEmpty) { |
| 39 | + val entry = aggregates.head.findClass(className) match { |
| 40 | + case s @ Some(_: SourceFileEntry) if isSource => s |
| 41 | + case s @ Some(_: ClassFileEntry) if !isSource => s |
| 42 | + case _ => None |
| 43 | + } |
| 44 | + if (entry.isDefined) entry |
| 45 | + else findEntry(aggregates.tail, isSource) |
| 46 | + } else None |
| 47 | + |
| 48 | + val classEntry = findEntry(aggregates, isSource = false) |
| 49 | + val sourceEntry = findEntry(aggregates, isSource = true) |
| 50 | + |
| 51 | + (classEntry, sourceEntry) match { |
| 52 | + case (Some(c: ClassFileEntry), Some(s: SourceFileEntry)) => Some(ClassAndSourceFilesEntry(c.file, s.file)) |
| 53 | + case (c @ Some(_), _) => c |
| 54 | + case (_, s) => s |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + override def asURLs: Seq[URL] = aggregates.flatMap(_.asURLs) |
| 59 | + |
| 60 | + override def asClassPathStrings: Seq[String] = aggregates.map(_.asClassPathString).distinct |
| 61 | + |
| 62 | + override def asSourcePathString: String = ClassPath.join(aggregates map (_.asSourcePathString): _*) |
| 63 | + |
| 64 | + override private[dotty] def packages(inPackage: String): Seq[PackageEntry] = { |
| 65 | + val aggregatedPackages = aggregates.flatMap(_.packages(inPackage)).distinct |
| 66 | + aggregatedPackages |
| 67 | + } |
| 68 | + |
| 69 | + override private[dotty] def classes(inPackage: String): Seq[ClassFileEntry] = |
| 70 | + getDistinctEntries(_.classes(inPackage)) |
| 71 | + |
| 72 | + override private[dotty] def sources(inPackage: String): Seq[SourceFileEntry] = |
| 73 | + getDistinctEntries(_.sources(inPackage)) |
| 74 | + |
| 75 | + override private[dotty] def list(inPackage: String): ClassPathEntries = { |
| 76 | + val (packages, classesAndSources) = aggregates.map { cp => |
| 77 | + try { |
| 78 | + cp.list(inPackage) |
| 79 | + } catch { |
| 80 | + case ex: java.io.IOException => |
| 81 | + val e = new FatalError(ex.getMessage) |
| 82 | + e.initCause(ex) |
| 83 | + throw e |
| 84 | + } |
| 85 | + }.unzip |
| 86 | + val distinctPackages = packages.flatten.distinct |
| 87 | + val distinctClassesAndSources = mergeClassesAndSources(classesAndSources: _*) |
| 88 | + ClassPathEntries(distinctPackages, distinctClassesAndSources) |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Returns only one entry for each name. If there's both a source and a class entry, it |
| 93 | + * creates an entry containing both of them. If there would be more than one class or source |
| 94 | + * entries for the same class it always would use the first entry of each type found on a classpath. |
| 95 | + */ |
| 96 | + private def mergeClassesAndSources(entries: Seq[ClassRepresentation]*): Seq[ClassRepresentation] = { |
| 97 | + // based on the implementation from MergedClassPath |
| 98 | + var count = 0 |
| 99 | + val indices = collection.mutable.HashMap[String, Int]() |
| 100 | + val mergedEntries = new ArrayBuffer[ClassRepresentation](1024) |
| 101 | + |
| 102 | + for { |
| 103 | + partOfEntries <- entries |
| 104 | + entry <- partOfEntries |
| 105 | + } { |
| 106 | + val name = entry.name |
| 107 | + if (indices contains name) { |
| 108 | + val index = indices(name) |
| 109 | + val existing = mergedEntries(index) |
| 110 | + |
| 111 | + if (existing.binary.isEmpty && entry.binary.isDefined) |
| 112 | + mergedEntries(index) = ClassAndSourceFilesEntry(entry.binary.get, existing.source.get) |
| 113 | + if (existing.source.isEmpty && entry.source.isDefined) |
| 114 | + mergedEntries(index) = ClassAndSourceFilesEntry(existing.binary.get, entry.source.get) |
| 115 | + } |
| 116 | + else { |
| 117 | + indices(name) = count |
| 118 | + mergedEntries += entry |
| 119 | + count += 1 |
| 120 | + } |
| 121 | + } |
| 122 | + mergedEntries.toIndexedSeq |
| 123 | + } |
| 124 | + |
| 125 | + private def getDistinctEntries[EntryType <: ClassRepresentation](getEntries: ClassPath => Seq[EntryType]): Seq[EntryType] = { |
| 126 | + val seenNames = collection.mutable.HashSet[String]() |
| 127 | + val entriesBuffer = new ArrayBuffer[EntryType](1024) |
| 128 | + for { |
| 129 | + cp <- aggregates |
| 130 | + entry <- getEntries(cp) if !seenNames.contains(entry.name) |
| 131 | + } { |
| 132 | + entriesBuffer += entry |
| 133 | + seenNames += entry.name |
| 134 | + } |
| 135 | + entriesBuffer.toIndexedSeq |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +object AggregateClassPath { |
| 140 | + def createAggregate(parts: ClassPath*): ClassPath = { |
| 141 | + val elems = new ArrayBuffer[ClassPath]() |
| 142 | + parts foreach { |
| 143 | + case AggregateClassPath(ps) => elems ++= ps |
| 144 | + case p => elems += p |
| 145 | + } |
| 146 | + if (elems.size == 1) elems.head |
| 147 | + else AggregateClassPath(elems.toIndexedSeq) |
| 148 | + } |
| 149 | +} |
0 commit comments