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

dev.denwav.hypo.model.AbstractClassDataProvider Maven / Gradle / Ivy

The newest version!
/*
 * Hypo, an extensible and pluggable Java bytecode analytical model.
 *
 * Copyright (C) 2023  Kyle Wood (DenWav)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License only.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

package dev.denwav.hypo.model;

import com.google.errorprone.annotations.ForOverride;
import dev.denwav.hypo.model.data.ClassData;
import dev.denwav.hypo.model.data.ClassKind;
import dev.denwav.hypo.model.data.FieldData;
import dev.denwav.hypo.model.data.HypoKey;
import dev.denwav.hypo.model.data.MethodData;
import dev.denwav.hypo.model.data.Visibility;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Default abstract implementation of {@link ClassDataProvider} based on {@link ClassProviderRoot}. Classes which
 * implement {@link ClassDataProvider} using this class as the base only need to override
 * {@link #parseClassData(byte[])}, this class implements all other requirements of {@link ClassDataProvider}, including
 * caching {@link ClassData} objects.
 *
 * 

This is the standard method of implementing {@link ClassDataProvider} and automatically makes any implementations * based on this class compatible with any {@link ClassProviderRoot} implementations as well. It is, however, not * a requirement for classes which implement {@link ClassDataProvider} to extend this class. * * @see ClassDataProvider * @see ClassProviderRoot */ public abstract class AbstractClassDataProvider implements ClassDataProvider { /** * Marker object to place in {@link #cache} to mark a class name as non-existent. This object should never be * returned to clients nor should its methods ever be called. */ private static final @NotNull ClassData NULL_DATA = createNullData(); /** * Cache which stores mappings between class names and their subsequent {@link ClassData}. {@link #NULL_DATA} is * used as a marker value for classes which cannot be found. * *

All class names should be normalized with {@link HypoModelUtil#normalizedClassName(String)} before being * passed to this map. */ private final @NotNull ConcurrentHashMap cache = new ConcurrentHashMap<>(); /** * {@link ClassProviderRoot Roots} to use for discovering class file data. */ private final @NotNull List<@NotNull ClassProviderRoot> rootProviders; /** * Decorator to use to decorate {@link ClassData} objects immediately after they are created. This must be * set prior to retrieving any classes from this provider, however it is usually not the responsibility of client * code to set this. * * @see #setDecorator(ClassDataDecorator) */ private @Nullable ClassDataDecorator decorator = null; /** * @see #isContextClassProvider() */ private boolean isContextClassProvider = false; /** * @see #isRequireFullClasspath() */ private boolean isRequireFullClasspath = false; /** * Create a new {@link AbstractClassDataProvider} using the given {@link ClassProviderRoot root providers}. * * @param rootProviders The root providers to use as the data source in this class data provider. */ protected AbstractClassDataProvider(final @NotNull List<@NotNull ClassProviderRoot> rootProviders) { this.rootProviders = rootProviders; } @Override public void setDecorator(final @NotNull ClassDataDecorator decorator) { this.decorator = decorator; } @Override public boolean isContextClassProvider() { return this.isContextClassProvider; } @Override public void setContextClassProvider(boolean contextClassProvider) { this.isContextClassProvider = contextClassProvider; } @Override public boolean isRequireFullClasspath() { return this.isRequireFullClasspath; } @Override public void setRequireFullClasspath(boolean requireFullClasspath) { this.isRequireFullClasspath = requireFullClasspath; } @Override @Contract("null -> null") public @Nullable ClassData findClass(final @Nullable String className) { if (className == null) { return null; } final ClassData result = this.cache.computeIfAbsent(this.normalize(className), k -> { try { final byte[] fileData = this.findFile(k + ".class"); if (fileData == null) { return NULL_DATA; } final ClassData res = this.parseClassData(fileData); return res != null ? this.decorate(res) : NULL_DATA; } catch (final IOException e) { throw HypoModelUtil.rethrow(e); } }); return result != NULL_DATA ? result : null; } private byte @Nullable [] findFile(final @NotNull String fileName) throws IOException { for (final ClassProviderRoot rootProvider : this.rootProviders) { final byte[] data = rootProvider.getClassData(fileName); if (data != null) { return data; } } return null; } private @NotNull String normalize(final @NotNull String className) { String fullClassName = className.endsWith(".class") ? className.substring(0, className.length() - 6) : className; fullClassName = HypoModelUtil.normalizedClassName(fullClassName); if (fullClassName.startsWith("/")) { fullClassName = fullClassName.substring(1); } return fullClassName; } @Override public @NotNull Stream stream() throws IOException { return this.rootProviders.stream() .flatMap(HypoModelUtil.wrapFunction(r -> r.getAllClasses().stream())) .map(ref -> this.cache.computeIfAbsent( this.normalize(ref.name()), HypoModelUtil.wrapFunction(k -> { final byte[] rawData = ref.readData(); if (rawData == null) { return NULL_DATA; } final ClassData result = this.parseClassData(rawData); return result != null ? this.decorate(result) : NULL_DATA; }) )); } /** *

* Given file data, parse it into a new {@link ClassData} object. This method should not attempt to cache the * output, as this class already handles caching of {@link ClassData} objects. Instead, this method should solely * parse the data into whichever implementation of {@link ClassData} it supports based on the format of the format * of the class data, and return it. *

* This method should also not be concerned with decorating the parsed class data using {@link #decorator}, the base * implementation will also handle that. *

* * @param file The raw binary data of the file to parse. * @return A new instance of {@link ClassData} corresponding to the given class file data. * @throws IOException If an IO error occurs while parsing the class file. */ @ForOverride protected abstract @Nullable ClassData parseClassData(final byte @NotNull [] file) throws IOException; @Contract("_ -> param1") private @NotNull ClassData decorate(final @NotNull ClassData classData) { if (this.decorator == null) { throw new IllegalStateException("ClassData requested before decorating ClassDataProvider"); } this.decorator.decorate(classData); classData.setContextClass(this.isContextClassProvider()); classData.setRequireFullClasspath(this.isRequireFullClasspath()); return classData; } @Override public void close() throws IOException { IOException thrown = null; for (final ClassProviderRoot rootProvider : this.rootProviders) { try { rootProvider.close(); } catch (final IOException e) { if (thrown == null) { thrown = e; } else { thrown.addSuppressed(e); } } } if (thrown != null) { throw thrown; } } @Override public @NotNull Collection roots() { return this.rootProviders; } private static @NotNull ClassData createNullData() { return new ClassData() { @Override public void setProvider(final @NotNull ClassDataProvider provider) { throw new IllegalStateException(); } @Override public boolean isContextClass() { throw new IllegalStateException(); } @Override public void setContextClass(boolean contextClass) { throw new IllegalStateException(); } @Override public boolean isRequireFullClasspath() { throw new IllegalStateException(); } @Override public void setRequireFullClasspath(boolean requireFullClasspath) { throw new IllegalStateException(); } @Override public @NotNull String name() { throw new IllegalStateException(); } @Override public @Nullable ClassData outerClass() { throw new IllegalStateException(); } @Override public boolean isStaticInnerClass() { throw new IllegalStateException(); } @Override public boolean isFinal() { throw new IllegalStateException(); } @Override public boolean isSynthetic() { throw new IllegalStateException(); } @Override public boolean isSealed() { throw new IllegalStateException(); } @Override public @Nullable List permittedClasses() { throw new IllegalStateException(); } @Override public @Nullable List<@NotNull FieldData> recordComponents() { throw new IllegalStateException(); } @Override public @NotNull EnumSet kinds() { throw new IllegalStateException(); } @Override public @NotNull Visibility visibility() { throw new IllegalStateException(); } @Override public @Nullable ClassData superClass() { throw new IllegalStateException(); } @Override public @NotNull List interfaces() { throw new IllegalStateException(); } @Override public @NotNull List fields() { throw new IllegalStateException(); } @Override public @NotNull List methods() { throw new IllegalStateException(); } @Override public @NotNull Set<@NotNull ClassData> childClasses() { throw new IllegalStateException(); } @Override public @NotNull Set<@NotNull ClassData> innerClasses() { throw new IllegalStateException(); } @Override public @Nullable T store(@NotNull HypoKey key, @Nullable T t) { throw new IllegalStateException(); } @Override public @NotNull T compute(@NotNull HypoKey key, @NotNull Supplier supplier) { throw new IllegalStateException(); } @Override public @Nullable T get(@NotNull HypoKey key) { throw new IllegalStateException(); } @Override public boolean contains(@NotNull HypoKey key) { throw new IllegalStateException(); } }; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy