All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.microbean.construct.ReadOnlyModularJavaFileManager Maven / Gradle / Ivy

The newest version!
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
 *
 * Copyright © 2024 microBean™.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.microbean.construct;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;

import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;

import java.lang.System.Logger;

import java.net.URI;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import java.util.concurrent.ConcurrentHashMap;

import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.element.NestingKind;
import javax.lang.model.element.Modifier;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;

import static java.lang.System.Logger.Level.DEBUG;

final class ReadOnlyModularJavaFileManager extends ForwardingJavaFileManager {


    /*
     * Static fields.
     */


    private static final Logger LOGGER = System.getLogger(ReadOnlyModularJavaFileManager.class.getName());

    private static final Set ALL_KINDS = EnumSet.allOf(JavaFileObject.Kind.class);


    /*
     * Instance fields.
     */


    private final Set locations;

    private final Map>> maps;


    /*
     * Constructors.
     */


    ReadOnlyModularJavaFileManager(final StandardJavaFileManager fm, final Collection moduleLocations) {
      super(fm);
      this.maps = new ConcurrentHashMap<>();
      this.locations = moduleLocations == null ? Set.of() : Set.copyOf(moduleLocations);
      if (LOGGER.isLoggable(DEBUG)) {
        LOGGER.log(DEBUG, "Module locations: " + this.locations);
      }
    }


    /*
     * Instance methods.
     */


    @Override
    public final void close() throws IOException {
      super.close();
      this.maps.clear();
    }

    @Override
    public final ClassLoader getClassLoader(final Location packageOrientedLocation) {
      assert !packageOrientedLocation.isModuleOrientedLocation();
      if (packageOrientedLocation instanceof ReadOnlyModuleLocation m) {
        return this.getClass().getModule().getLayer().findLoader(m.getName());
      }
      return super.getClassLoader(packageOrientedLocation);
    }

    @Override
    public final JavaFileObject getJavaFileForInput(final Location packageOrientedLocation,
                                                    final String className,
                                                    final JavaFileObject.Kind kind) throws IOException {
      if (packageOrientedLocation instanceof ReadOnlyModuleLocation m) {
        try (final ModuleReader mr = m.moduleReference().open()) {
          return mr.find(className.replace('.', '/') + kind.extension)
            .map(u -> new JavaFileRecord(kind, className, u))
            .orElse(null);
        } catch (final IOException e) {
          throw new UncheckedIOException(e.getMessage(), e);
        }
      }
      return super.getJavaFileForInput(packageOrientedLocation, className, kind);
    }

    @Override
    public final boolean hasLocation(final Location location) {
      return switch (location) {
      case null -> false;
      case StandardLocation s -> switch (s) {
      case CLASS_PATH, MODULE_PATH, PATCH_MODULE_PATH, PLATFORM_CLASS_PATH, SYSTEM_MODULES, UPGRADE_MODULE_PATH -> {
        assert !s.isOutputLocation();
        yield super.hasLocation(s);
      }
      case ANNOTATION_PROCESSOR_MODULE_PATH, ANNOTATION_PROCESSOR_PATH, CLASS_OUTPUT, MODULE_SOURCE_PATH, NATIVE_HEADER_OUTPUT, SOURCE_OUTPUT, SOURCE_PATH -> false;
      };
      case ReadOnlyModuleLocation m -> true;
      default -> !location.isOutputLocation() && super.hasLocation(location);
      };
    }

    @Override
    public final Iterable list(final Location packageOrientedLocation,
                                               final String packageName,
                                               final Set kinds,
                                               final boolean recurse)
      throws IOException {
      if (packageOrientedLocation instanceof ReadOnlyModuleLocation m) {
        final ModuleReference mref = m.moduleReference();
        if (recurse) {
          // Don't cache anything; not really worth it
          try (final ModuleReader reader = mref.open()) {
            return list(reader, packageName, kinds, true);
          }
        }
        final Map> m0 = this.maps.computeIfAbsent(mref, mr -> {
            try (final ModuleReader reader = mr.open();
                 final Stream ss = reader.list()) {
              return
                Collections.unmodifiableMap(ss.filter(s -> !s.endsWith("/"))
                                            .collect(HashMap::new,
                                                     (map, s) -> {
                                                       // s is, e.g., "foo/Bar.class"
                                                       final int lastSlashIndex = s.lastIndexOf('/');
                                                       assert lastSlashIndex != 0;
                                                       final String p0 = lastSlashIndex > 0 ? s.substring(0, lastSlashIndex).replace('/', '.') : "";
                                                       // p0 is now "foo"; list will be class files under package foo
                                                       final List list = map.computeIfAbsent(p0, p1 -> new ArrayList<>());
                                                       final JavaFileObject.Kind kind = kind(s);
                                                       try {
                                                         list.add(new JavaFileRecord(kind,
                                                                                     kind == JavaFileObject.Kind.CLASS || kind == JavaFileObject.Kind.SOURCE ?
                                                                                     s.substring(0, s.length() - kind.extension.length()).replace('/', '.') :
                                                                                     null,
                                                                                     reader.find(s).orElseThrow()));
                                                       } catch (final IOException ioException) {
                                                         throw new UncheckedIOException(ioException.getMessage(), ioException);
                                                       }
                                                     },
                                                     Map::putAll));
            } catch (final IOException ioException) {
              throw new UncheckedIOException(ioException.getMessage(), ioException);
            }
          });
        List unfilteredPackageContents = m0.get(packageName);
        if (unfilteredPackageContents == null) {
          return List.of();
        }
        assert !unfilteredPackageContents.isEmpty();
        if (kinds.size() < ALL_KINDS.size()) {
          unfilteredPackageContents = new ArrayList<>(unfilteredPackageContents);
          unfilteredPackageContents.removeIf(f -> !kinds.contains(f.kind()));
        }
        return Collections.unmodifiableList(unfilteredPackageContents);
      }
      return super.list(packageOrientedLocation, packageName, kinds, recurse);
    }

    // Returns package-oriented locations (or output locations if the given moduleOrientedOrOutputLocation is an output
    // location).
    @Override
    public final Iterable> listLocationsForModules(final Location moduleOrientedOrOutputLocation) throws IOException {
      return
        moduleOrientedOrOutputLocation == StandardLocation.MODULE_PATH ?
        List.of(this.locations) :
        super.listLocationsForModules(moduleOrientedOrOutputLocation);
    }

    @Override
    public final String inferBinaryName(final Location packageOrientedLocation, final JavaFileObject file) {
      return file instanceof JavaFileRecord f ? f.binaryName() : super.inferBinaryName(packageOrientedLocation, file);
    }

    @Override
    public final String inferModuleName(final Location packageOrientedLocation) throws IOException {
      if (packageOrientedLocation instanceof ReadOnlyModuleLocation m) {
        assert m.getName() != null : "m.getName() == null: " + m;
        return m.getName();
      }
      return super.inferModuleName(packageOrientedLocation);
    }

    @Override
    public final boolean contains(final Location packageOrModuleOrientedLocation, final FileObject fo) throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final FileObject getFileForInput(final Location packageOrientedLocation,
                                            final String packageName,
                                            final String relativeName)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final FileObject getFileForOutput(final Location outputLocation,
                                             final String packageName,
                                             final String relativeName,
                                             final FileObject sibling)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final FileObject getFileForOutputForOriginatingFiles(final Location outputLocation,
                                                                final String packageName,
                                                                final String relativeName,
                                                                final FileObject... originatingFiles)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final JavaFileObject getJavaFileForOutput(final Location packageOrientedLocation,
                                                     final String className,
                                                     final JavaFileObject.Kind kind,
                                                     final FileObject sibling)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final JavaFileObject getJavaFileForOutputForOriginatingFiles(final Location packageOrientedLocation,
                                                                        final String className,
                                                                        final JavaFileObject.Kind kind,
                                                                        final FileObject... originatingFiles)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    // Returns a module-oriented location or an output location.
    @Override
    public final Location getLocationForModule(final Location moduleOrientedOrOutputLocation,
                                               final String moduleName) throws IOException {
      throw new UnsupportedOperationException();
    }

    // Returns a module-oriented location or an output location.
    @Override
    public final Location getLocationForModule(final Location moduleOrientedOrOutputLocation,
                                               final JavaFileObject fileObject)
      throws IOException {
      throw new UnsupportedOperationException();
    }

    @Override
    public final boolean isSameFile(final FileObject a, final FileObject b) {
      throw new UnsupportedOperationException();
    }


    /*
     * Static methods.
     */


    private static final JavaFileObject.Kind kind(final String s) {
      return kind(ALL_KINDS, s);
    }

    private static final JavaFileObject.Kind kind(final Iterable kinds, final String s) {
      for (final JavaFileObject.Kind kind : kinds) {
        if (kind != JavaFileObject.Kind.OTHER && s.endsWith(kind.extension)) {
          return kind;
        }
      }
      return JavaFileObject.Kind.OTHER;
    }

    private static final Iterable list(final ModuleReader mr,
                                                       final String packageName,
                                                       final Set kinds,
                                                       final boolean recurse)
      throws IOException {
      final String p = packageName.replace('.', '/');
      final int packagePrefixLength = p.length() + 1;
      try (final Stream ss = mr.list()) {
        return ss
          .filter(s ->
                  !s.endsWith("/") &&
                  s.startsWith(p) &&
                  isAKind(kinds, s) &&
                  (recurse || s.indexOf('/', packagePrefixLength) < 0))
          .map(s -> {
              final JavaFileObject.Kind kind = kind(kinds, s);
              try {
                return
                  new JavaFileRecord(kind,
                                     kind == JavaFileObject.Kind.CLASS || kind == JavaFileObject.Kind.SOURCE ?
                                     s.substring(0, s.length() - kind.extension.length()).replace('/', '.') :
                                     null,
                                     mr.find(s).orElseThrow());
              } catch (final IOException ioException) {
                throw new UncheckedIOException(ioException.getMessage(), ioException);
              }
            })
          .collect(Collectors.toUnmodifiableList());
      }
    }

    private static final boolean isAKind(final Set kinds, final String moduleResource) {
      for (final JavaFileObject.Kind k : kinds) {
        if (moduleResource.endsWith(k.extension)) {
          return true;
        }
      }
      return false;
    }

  }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy