Reflection (#1085)

* index instead of reflection

* Update update.xml
This commit is contained in:
Bane Stojkovic
2026-10-13 21:17:30 +02:00
committed by GitHub
parent 2397ea2021
commit 42d3d557c6
24 changed files with 560 additions and 149 deletions
@@ -24,11 +24,13 @@ package org.diylc.common;
import java.util.List;
import java.util.Set;
import org.diylc.core.annotations.Indexed;
import org.diylc.netlist.Netlist;
import org.diylc.netlist.Node;
import org.diylc.netlist.Summary;
import org.diylc.netlist.TreeException;
@Indexed
public interface INetlistAnalyzer {
/**
@@ -29,6 +29,7 @@ import java.util.UUID;
import org.diylc.core.annotations.ComponentDescriptor;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.Indexed;
/**
* Interface for component instance. Implementation classes of this interface will be instantiated
@@ -47,6 +48,7 @@ import org.diylc.core.annotations.EditableProperty;
*
* @param <T> type of component values, e.g. Resistance for resistors or String for transistors.
*/
@Indexed
public interface IDIYComponent<T> extends Serializable, Cloneable {
public static final int CHASSIS = 1;
@@ -30,6 +30,7 @@ import org.diylc.core.IDIYComponent;
*
* @author Branislav Stojkovic
*/
@Indexed
public interface IAutoCreator {
/**
@@ -0,0 +1,44 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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 3 of the License, or
(at your option) any later version.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.core.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.diylc.utils.ReflectionUtils;
/**
* Marks a type whose concrete implementations are discovered at runtime. The
* {@code diylc-processor} annotation processor lists them under {@code META-INF/services} when
* the implementing module is compiled, and {@link ReflectionUtils#getIndexedClasses(Class)} reads
* that list back, so nothing has to scan the classpath. An implementation is picked up just by
* implementing the type; there is no registry to update.
*
* @author Branislav Stojkovic
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Indexed {
}
@@ -2,6 +2,9 @@ package org.diylc.netlist;
import java.util.List;
import org.diylc.core.annotations.Indexed;
@Indexed
public interface IGuitarDiagramAnalyzer {
List<String> collectNotes(Netlist netlist);
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import org.diylc.core.IDIYComponent;
import org.diylc.core.annotations.Indexed;
/*
*
@@ -41,6 +42,7 @@ import org.diylc.core.IDIYComponent;
* <http://www.gnu.org/licenses/>.
*
*/
@Indexed
public interface INetlistParser {
String getName();
@@ -0,0 +1,90 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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 3 of the License, or
(at your option) any later version.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.presenter;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import org.apache.log4j.Logger;
import org.diylc.core.IDIYComponent;
/**
* Toolbox icon of a component type. Drawing it means instantiating the component, so it is
* deferred until the icon is first painted rather than done for every type at startup.
*
* @author Branislav Stojkovic
*/
class ComponentIcon implements Icon {
private static final Logger LOG = Logger.getLogger(ComponentIcon.class);
private final Class<? extends IDIYComponent<?>> componentClass;
private BufferedImage image;
ComponentIcon(Class<? extends IDIYComponent<?>> componentClass) {
this.componentClass = componentClass;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (image == null) {
image = drawImage();
}
g.drawImage(image, x, y, null);
}
@Override
public int getIconWidth() {
return Presenter.ICON_SIZE;
}
@Override
public int getIconHeight() {
return Presenter.ICON_SIZE;
}
private BufferedImage drawImage() {
BufferedImage image =
new BufferedImage(Presenter.ICON_SIZE, Presenter.ICON_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
try {
IDIYComponent<?> componentInstance = componentClass.getDeclaredConstructor().newInstance();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
componentInstance.drawIcon(g2d, Presenter.ICON_SIZE, Presenter.ICON_SIZE);
} catch (Exception e) {
LOG.error("Error drawing component icon for " + componentClass.getName(), e);
} finally {
g2d.dispose();
}
return image;
}
}
@@ -21,19 +21,13 @@
*/
package org.diylc.presenter;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.apache.log4j.Logger;
import org.diylc.common.ComponentType;
@@ -53,11 +47,7 @@ import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.KeywordPolicy;
import org.diylc.lang.LangUtil;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import static org.reflections.scanners.Scanners.TypesAnnotated;
import org.diylc.utils.ReflectionUtils;
/**
* Utility class with component processing methods.
@@ -138,26 +128,7 @@ public class ComponentProcessor {
} else { // default
return null;
}
icon = null;
// Draw component icon.
try {
IDIYComponent<?> componentInstance = (IDIYComponent<?>) clazz.getDeclaredConstructor().newInstance();
Image image =
new BufferedImage(Presenter.ICON_SIZE, Presenter.ICON_SIZE, java.awt.image.BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
// RenderingHints.VALUE_STROKE_PURE);
componentInstance.drawIcon(g2d, Presenter.ICON_SIZE, Presenter.ICON_SIZE);
icon = new ImageIcon(image);
} catch (Exception e) {
LOG.error("Error drawing component icon", e);
}
icon = new ComponentIcon(clazz);
ComponentType componentType =
new ComponentType(name, description, creationMethod, category, namePrefix, author, icon, clazz, zOrder,
flexibleZOrder, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag, cacheDrawing, datasheet,
@@ -254,64 +225,57 @@ public class ComponentProcessor {
if (componentTypes == null) {
LOG.info("Loading component types.");
componentTypes = new HashMap<String, List<ComponentType>>();
Reflections reflections = new Reflections(
new ConfigurationBuilder()
.forPackage("org.diylc")
.filterInputsBy(new FilterBuilder().includePackage("org.diylc.components"))
.setScanners(TypesAnnotated));
Set<Class<?>> componentTypeClasses = null;
try {
componentTypeClasses = reflections.getTypesAnnotatedWith(ComponentDescriptor.class, false);
List<Map<String, Object>> componentSnapshots = LOG_COMPONENT_TYPE_DETAILS ? new ArrayList<>() : null;
for (Class<?> clazz : componentTypeClasses) {
if (!Modifier.isAbstract(clazz.getModifiers()) && IDIYComponent.class.isAssignableFrom(clazz)) {
ComponentType componentType =
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
if (componentType == null)
continue;
for (Class<?> clazz : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) {
ComponentType componentType =
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
if (componentType == null) {
continue;
}
// just to store in the cache
ComponentProcessor.getInstance().extractProperties(clazz);
// just to store in the cache
ComponentProcessor.getInstance().extractProperties(clazz);
// types created only programmatically (e.g. CompositeComponent) don't belong in the
// palette, but still need to go through extractProperties() above so the cache is warm
if (clazz.getAnnotation(ComponentDescriptor.class).hiddenInPalette())
continue;
// types created only programmatically (e.g. CompositeComponent) don't belong in the
// palette, but still need to go through extractProperties() above so the cache is warm
if (clazz.getAnnotation(ComponentDescriptor.class).hiddenInPalette()) {
continue;
}
List<ComponentType> nestedList;
if (componentTypes.containsKey(componentType.getCategory())) {
nestedList = componentTypes.get(componentType.getCategory());
} else {
nestedList = new ArrayList<ComponentType>();
componentTypes.put(componentType.getCategory(), nestedList);
}
nestedList.add(componentType);
List<ComponentType> nestedList;
if (componentTypes.containsKey(componentType.getCategory())) {
nestedList = componentTypes.get(componentType.getCategory());
} else {
nestedList = new ArrayList<ComponentType>();
componentTypes.put(componentType.getCategory(), nestedList);
}
nestedList.add(componentType);
if (LOG_COMPONENT_TYPE_DETAILS) {
try {
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("name", componentType.getName());
snapshot.put("description", componentType.getDescription());
snapshot.put("creationMethod", componentType.getCreationMethod());
snapshot.put("category", componentType.getCategory());
List<PropertyWrapper> props = propertyCache.get(clazz.getName());
snapshot.put("properties", props != null ? props.stream()
.map(p -> {
Map<String, Object> prop = new LinkedHashMap<>();
prop.put("name", p.getName());
prop.put("type", formatPropertyType(p.getType()));
List<String> possibleValues = getPossibleValues(p.getType());
if (possibleValues != null) {
prop.put("possibleValues", possibleValues);
}
return prop;
})
.collect(Collectors.toList()) : Collections.emptyList());
componentSnapshots.add(snapshot);
} catch (Exception e) {
LOG.warn("Failed to log component type details for " + clazz.getName(), e);
}
if (LOG_COMPONENT_TYPE_DETAILS) {
try {
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("name", componentType.getName());
snapshot.put("description", componentType.getDescription());
snapshot.put("creationMethod", componentType.getCreationMethod());
snapshot.put("category", componentType.getCategory());
List<PropertyWrapper> props = propertyCache.get(clazz.getName());
snapshot.put("properties", props != null ? props.stream()
.map(p -> {
Map<String, Object> prop = new LinkedHashMap<>();
prop.put("name", p.getName());
prop.put("type", formatPropertyType(p.getType()));
List<String> possibleValues = getPossibleValues(p.getType());
if (possibleValues != null) {
prop.put("possibleValues", possibleValues);
}
return prop;
})
.collect(Collectors.toList()) : Collections.emptyList());
componentSnapshots.add(snapshot);
} catch (Exception e) {
LOG.warn("Failed to log component type details for " + clazz.getName(), e);
}
}
}
@@ -31,7 +31,6 @@ import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -3133,16 +3132,8 @@ public class Presenter implements IPlugInPort {
@Override
public List<INetlistAnalyzer> getNetlistAnalyzers() {
Set<Class<?>> classes;
try {
classes = Utils.getClasses("org.diylc.netlist");
List<INetlistAnalyzer> result = new ArrayList<INetlistAnalyzer>();
for (Class<?> clazz : classes) {
if (!Modifier.isAbstract(clazz.getModifiers()) && INetlistAnalyzer.class.isAssignableFrom(clazz)) {
result.add((INetlistAnalyzer) clazz.getDeclaredConstructor().newInstance());
}
}
List<INetlistAnalyzer> result = ReflectionUtils.getIndexedInstances(INetlistAnalyzer.class);
result.sort(new Comparator<INetlistAnalyzer>() {
@@ -26,7 +26,9 @@ import java.util.List;
import org.w3c.dom.Element;
import org.diylc.core.Project;
import org.diylc.core.annotations.Indexed;
@Indexed
public interface IOldFileParser {
boolean canParse(String version);
@@ -30,7 +30,6 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -41,7 +40,6 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.appframework.simplemq.MessageDispatcher;
import org.diylc.appframework.update.VersionNumber;
import org.w3c.dom.Document;
@@ -59,6 +57,7 @@ import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.presenter.Presenter;
import org.diylc.test.DIYTest;
import org.diylc.utils.ReflectionUtils;
public class ProjectFileManager {
@@ -144,17 +143,10 @@ public class ProjectFileManager {
public List<IOldFileParser> getParsers() {
if (parsers == null) {
parsers = new ArrayList<IOldFileParser>();
Set<Class<?>> componentTypeClasses = null;
try {
componentTypeClasses = Utils.getClasses("org.diylc.serialization");
for (Class<?> clazz : componentTypeClasses) {
if (!Modifier.isAbstract(clazz.getModifiers()) && IOldFileParser.class.isAssignableFrom(clazz)) {
IOldFileParser instance = (IOldFileParser) clazz.getDeclaredConstructor().newInstance();
parsers.add(instance);
}
}
parsers = ReflectionUtils.getIndexedInstances(IOldFileParser.class);
} catch (Exception e) {
parsers = new ArrayList<IOldFileParser>();
LOG.error("Could not find old version parsers", e);
}
}
@@ -21,23 +21,30 @@
*/
package org.diylc.utils;
import java.lang.reflect.Modifier;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.core.annotations.IAutoCreator;
import org.diylc.core.annotations.Indexed;
import org.diylc.netlist.IGuitarDiagramAnalyzer;
import org.diylc.netlist.INetlistParser;
import org.diylc.presenter.Presenter;
public class ReflectionUtils {
private static final Logger LOG = Logger.getLogger(Presenter.class);
private static final Logger LOG = Logger.getLogger(ReflectionUtils.class);
private static final String SERVICES_DIR = "META-INF/services/";
/**
* {@link List} of {@link IAutoCreator} objects that are capable of creating more components
@@ -45,19 +52,66 @@ public class ReflectionUtils {
*/
private static List<IAutoCreator> autoCreators;
public static List<IAutoCreator> getAutoCreators() {
if (autoCreators == null) {
autoCreators = new ArrayList<IAutoCreator>();
Set<Class<?>> classes = null;
try {
classes = Utils.getClasses("org.diylc.components.autocreate");
for (Class<?> clazz : classes) {
if (IAutoCreator.class.isAssignableFrom(clazz)) {
autoCreators.add((IAutoCreator) clazz.getDeclaredConstructor().newInstance());
LOG.debug("Loaded auto-creator: " + clazz.getName());
/**
* Returns the concrete implementations of an {@link Indexed} type, as listed at build time by
* {@code diylc-processor}. Classes are loaded but not initialized.
*/
public static <T> List<Class<? extends T>> getIndexedClasses(Class<T> type) {
ClassLoader classLoader = ReflectionUtils.class.getClassLoader();
// each module jar carries its own index file for the same type
Set<String> classNames = new LinkedHashSet<String>();
try {
Enumeration<URL> indexFiles = classLoader.getResources(SERVICES_DIR + type.getName());
while (indexFiles.hasMoreElements()) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(indexFiles.nextElement().openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
classNames.add(line);
}
}
}
}
} catch (IOException e) {
LOG.error("Could not read the class index of " + type.getName(), e);
}
List<Class<? extends T>> result = new ArrayList<Class<? extends T>>(classNames.size());
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className, false, classLoader);
if (type.isAssignableFrom(clazz)) {
result.add(clazz.asSubclass(type));
} else {
LOG.warn("Class index of " + type.getName() + " lists unrelated class " + className);
}
} catch (ClassNotFoundException e) {
LOG.warn("Class index of " + type.getName() + " lists missing class " + className);
}
}
return result;
}
/**
* Instantiates every class returned by {@link #getIndexedClasses(Class)} through its no-arg
* constructor.
*/
public static <T> List<T> getIndexedInstances(Class<T> type) throws ReflectiveOperationException {
List<T> result = new ArrayList<T>();
for (Class<? extends T> clazz : getIndexedClasses(type)) {
result.add(clazz.getDeclaredConstructor().newInstance());
}
return result;
}
public static List<IAutoCreator> getAutoCreators() {
if (autoCreators == null) {
try {
autoCreators = getIndexedInstances(IAutoCreator.class);
} catch (Exception e) {
autoCreators = new ArrayList<IAutoCreator>();
LOG.error("Error loading auto-creator types", e);
}
}
@@ -65,16 +119,8 @@ public class ReflectionUtils {
}
public static List<INetlistParser> getNetlistParserDefinitions() {
Set<Class<?>> classes;
try {
classes = Utils.getClasses("org.diylc.netlist");
List<INetlistParser> result = new ArrayList<INetlistParser>();
for (Class<?> clazz : classes) {
if (!Modifier.isAbstract(clazz.getModifiers()) && INetlistParser.class.isAssignableFrom(clazz)) {
result.add((INetlistParser) clazz.getDeclaredConstructor().newInstance());
}
}
List<INetlistParser> result = getIndexedInstances(INetlistParser.class);
Collections.sort(result, new Comparator<INetlistParser>() {
@@ -92,16 +138,7 @@ public class ReflectionUtils {
}
public static IGuitarDiagramAnalyzer getGuitarDiagramAnalyzer() throws Exception {
Set<Class<?>> classes;
classes = Utils.getClasses("org.diylc.netlist");
List<IGuitarDiagramAnalyzer> result = new ArrayList<>();
for (Class<?> clazz : classes) {
if (!Modifier.isAbstract(clazz.getModifiers()) && IGuitarDiagramAnalyzer.class.isAssignableFrom(clazz)) {
result.add((IGuitarDiagramAnalyzer) clazz.getDeclaredConstructor().newInstance());
}
}
List<IGuitarDiagramAnalyzer> result = getIndexedInstances(IGuitarDiagramAnalyzer.class);
if (result.isEmpty()) {
return null;
@@ -611,7 +611,11 @@
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improve performance of 'Project Explorer' pane</description>
<description>Improved performance of application initialization</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved performance of 'Project Explorer' pane</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>