mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
@@ -7,7 +7,7 @@ that file before editing anything under `diylc/`.
|
|||||||
|
|
||||||
| Path | Contents |
|
| Path | Contents |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `diylc/` | The application. Maven reactor with `diylc-core`, `diylc-library`, `diylc-swing`. This is where nearly all work happens. |
|
| `diylc/` | The application. Maven reactor with `diylc-core`, `diylc-library`, `diylc-swing`, and the build-time `diylc-processor`. This is where nearly all work happens. |
|
||||||
| `diylc-regression-data/` | Sample `.diy` projects plus reference PNG and netlist outputs used by the regression suite, and the historical reports. |
|
| `diylc-regression-data/` | Sample `.diy` projects plus reference PNG and netlist outputs used by the regression suite, and the historical reports. |
|
||||||
| `diylc-server-api/` | PHP backend for cloud and collaboration features. Runs in an isolated environment on diy-fever.com and cannot be tested locally — do not modify unless explicitly asked. |
|
| `diylc-server-api/` | PHP backend for cloud and collaboration features. Runs in an isolated environment on diy-fever.com and cannot be tested locally — do not modify unless explicitly asked. |
|
||||||
| `snap/` | Snap packaging metadata. |
|
| `snap/` | Snap packaging metadata. |
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ description: Add a new component type to the DIYLC library, or substantially mod
|
|||||||
# Adding a component to DIYLC
|
# Adding a component to DIYLC
|
||||||
|
|
||||||
Components live in `diylc-library/src/main/java/org/diylc/components/`. They are discovered at
|
Components live in `diylc-library/src/main/java/org/diylc/components/`. They are discovered at
|
||||||
runtime by Reflections scanning `org.diylc` — **there is no registry to update**. A class becomes a
|
runtime from a class index that `diylc-processor` writes at compile time — **there is no registry
|
||||||
|
to update**. A class becomes a
|
||||||
component by carrying `@ComponentDescriptor` and implementing `IDIYComponent` (in practice, by
|
component by carrying `@ComponentDescriptor` and implementing `IDIYComponent` (in practice, by
|
||||||
extending one of the abstract bases).
|
extending one of the abstract bases).
|
||||||
|
|
||||||
|
|||||||
+8
-4
@@ -15,14 +15,15 @@ if it compiles and works.
|
|||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
Maven reactor rooted at `diylc/pom.xml`, version `6.4.0`, three modules with a strict one-way
|
Maven reactor rooted at `diylc/pom.xml`, version `6.4.0`, three application modules with a strict
|
||||||
dependency chain:
|
one-way dependency chain, plus a build-time annotation processor:
|
||||||
|
|
||||||
| Module | Contains | Depends on |
|
| Module | Contains | Depends on |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `diylc-core` | Domain model, `Presenter`, rendering pipeline, messaging, serialization, netlist engine, annotations | — |
|
| `diylc-core` | Domain model, `Presenter`, rendering pipeline, messaging, serialization, netlist engine, annotations | — |
|
||||||
| `diylc-library` | Concrete component classes, transformers, datasheets, legacy file parsers, component-aware analyzers | `diylc-core` |
|
| `diylc-library` | Concrete component classes, transformers, datasheets, legacy file parsers, component-aware analyzers | `diylc-core` |
|
||||||
| `diylc-swing` | Swing shell, plugins, dialogs, editors, `DIYLCStarter` | `diylc-core`, `diylc-library` |
|
| `diylc-swing` | Swing shell, plugins, dialogs, editors, `DIYLCStarter` | `diylc-core`, `diylc-library` |
|
||||||
|
| `diylc-processor` | `ServiceIndexProcessor`, run by `javac` over the three modules above; never on the runtime classpath | — |
|
||||||
|
|
||||||
**`diylc-core` must never reference a concrete component type.** It knows components only through
|
**`diylc-core` must never reference a concrete component type.** It knows components only through
|
||||||
`IDIYComponent` and the annotations. If core logic seems to need knowledge of a specific component,
|
`IDIYComponent` and the annotations. If core logic seems to need knowledge of a specific component,
|
||||||
@@ -30,8 +31,11 @@ that knowledge belongs in `diylc-library` (an analyzer, a transformer, a parser)
|
|||||||
through a new interface in core that the component implements — look at
|
through a new interface in core that the component implements — look at
|
||||||
`org.diylc.core.gerber.IGerberComponentSimple` for the established shape of that escape hatch.
|
`org.diylc.core.gerber.IGerberComponentSimple` for the established shape of that escape hatch.
|
||||||
|
|
||||||
Component and plugin classes are discovered at runtime by Reflections scanning the `org.diylc`
|
Components, netlist analyzers and parsers, legacy file parsers and auto-creators are discovered
|
||||||
package (`ComponentProcessor`). There is no registry to update when adding a component.
|
through a class index built at compile time: every concrete class implementing a type marked
|
||||||
|
`@Indexed` (`IDIYComponent`, `INetlistAnalyzer`, ...) is listed under `META-INF/services/` by
|
||||||
|
`diylc-processor`, and `ReflectionUtils.getIndexedClasses()` reads that back at startup. Nothing
|
||||||
|
scans the classpath, and there is no registry to update when adding a component.
|
||||||
|
|
||||||
## Build and verify
|
## Build and verify
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Internal module dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.diylc</groupId>
|
||||||
|
<artifactId>diylc-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Common dependencies from Maven Central -->
|
<!-- Common dependencies from Maven Central -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>log4j</groupId>
|
<groupId>log4j</groupId>
|
||||||
@@ -52,11 +58,6 @@
|
|||||||
<artifactId>jettison</artifactId>
|
<artifactId>jettison</artifactId>
|
||||||
<version>1.5.4</version>
|
<version>1.5.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.reflections</groupId>
|
|
||||||
<artifactId>reflections</artifactId>
|
|
||||||
<version>0.10.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ package org.diylc.common;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.diylc.core.annotations.Indexed;
|
||||||
import org.diylc.netlist.Netlist;
|
import org.diylc.netlist.Netlist;
|
||||||
import org.diylc.netlist.Node;
|
import org.diylc.netlist.Node;
|
||||||
import org.diylc.netlist.Summary;
|
import org.diylc.netlist.Summary;
|
||||||
import org.diylc.netlist.TreeException;
|
import org.diylc.netlist.TreeException;
|
||||||
|
|
||||||
|
@Indexed
|
||||||
public interface INetlistAnalyzer {
|
public interface INetlistAnalyzer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import org.diylc.core.annotations.ComponentDescriptor;
|
import org.diylc.core.annotations.ComponentDescriptor;
|
||||||
import org.diylc.core.annotations.EditableProperty;
|
import org.diylc.core.annotations.EditableProperty;
|
||||||
|
import org.diylc.core.annotations.Indexed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for component instance. Implementation classes of this interface will be instantiated
|
* 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.
|
* @param <T> type of component values, e.g. Resistance for resistors or String for transistors.
|
||||||
*/
|
*/
|
||||||
|
@Indexed
|
||||||
public interface IDIYComponent<T> extends Serializable, Cloneable {
|
public interface IDIYComponent<T> extends Serializable, Cloneable {
|
||||||
|
|
||||||
public static final int CHASSIS = 1;
|
public static final int CHASSIS = 1;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.diylc.core.IDIYComponent;
|
|||||||
*
|
*
|
||||||
* @author Branislav Stojkovic
|
* @author Branislav Stojkovic
|
||||||
*/
|
*/
|
||||||
|
@Indexed
|
||||||
public interface IAutoCreator {
|
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 java.util.List;
|
||||||
|
|
||||||
|
import org.diylc.core.annotations.Indexed;
|
||||||
|
|
||||||
|
@Indexed
|
||||||
public interface IGuitarDiagramAnalyzer {
|
public interface IGuitarDiagramAnalyzer {
|
||||||
|
|
||||||
List<String> collectNotes(Netlist netlist);
|
List<String> collectNotes(Netlist netlist);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.diylc.core.IDIYComponent;
|
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/>.
|
* <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Indexed
|
||||||
public interface INetlistParser {
|
public interface INetlistParser {
|
||||||
|
|
||||||
String getName();
|
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;
|
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.Method;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import org.diylc.common.ComponentType;
|
import org.diylc.common.ComponentType;
|
||||||
@@ -53,11 +47,7 @@ import org.diylc.core.annotations.EditableProperty;
|
|||||||
import org.diylc.core.annotations.KeywordPolicy;
|
import org.diylc.core.annotations.KeywordPolicy;
|
||||||
|
|
||||||
import org.diylc.lang.LangUtil;
|
import org.diylc.lang.LangUtil;
|
||||||
import org.reflections.Reflections;
|
import org.diylc.utils.ReflectionUtils;
|
||||||
import org.reflections.util.ConfigurationBuilder;
|
|
||||||
import org.reflections.util.FilterBuilder;
|
|
||||||
|
|
||||||
import static org.reflections.scanners.Scanners.TypesAnnotated;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class with component processing methods.
|
* Utility class with component processing methods.
|
||||||
@@ -138,26 +128,7 @@ public class ComponentProcessor {
|
|||||||
} else { // default
|
} else { // default
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
icon = null;
|
icon = new ComponentIcon(clazz);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
ComponentType componentType =
|
ComponentType componentType =
|
||||||
new ComponentType(name, description, creationMethod, category, namePrefix, author, icon, clazz, zOrder,
|
new ComponentType(name, description, creationMethod, category, namePrefix, author, icon, clazz, zOrder,
|
||||||
flexibleZOrder, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag, cacheDrawing, datasheet,
|
flexibleZOrder, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag, cacheDrawing, datasheet,
|
||||||
@@ -254,64 +225,57 @@ public class ComponentProcessor {
|
|||||||
if (componentTypes == null) {
|
if (componentTypes == null) {
|
||||||
LOG.info("Loading component types.");
|
LOG.info("Loading component types.");
|
||||||
componentTypes = new HashMap<String, List<ComponentType>>();
|
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 {
|
try {
|
||||||
componentTypeClasses = reflections.getTypesAnnotatedWith(ComponentDescriptor.class, false);
|
|
||||||
List<Map<String, Object>> componentSnapshots = LOG_COMPONENT_TYPE_DETAILS ? new ArrayList<>() : null;
|
List<Map<String, Object>> componentSnapshots = LOG_COMPONENT_TYPE_DETAILS ? new ArrayList<>() : null;
|
||||||
|
|
||||||
for (Class<?> clazz : componentTypeClasses) {
|
for (Class<?> clazz : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) {
|
||||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IDIYComponent.class.isAssignableFrom(clazz)) {
|
ComponentType componentType =
|
||||||
ComponentType componentType =
|
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
|
||||||
ComponentProcessor.getInstance().extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clazz);
|
if (componentType == null) {
|
||||||
if (componentType == null)
|
continue;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
// just to store in the cache
|
// just to store in the cache
|
||||||
ComponentProcessor.getInstance().extractProperties(clazz);
|
ComponentProcessor.getInstance().extractProperties(clazz);
|
||||||
|
|
||||||
// types created only programmatically (e.g. CompositeComponent) don't belong in the
|
// 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
|
// palette, but still need to go through extractProperties() above so the cache is warm
|
||||||
if (clazz.getAnnotation(ComponentDescriptor.class).hiddenInPalette())
|
if (clazz.getAnnotation(ComponentDescriptor.class).hiddenInPalette()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<ComponentType> nestedList;
|
List<ComponentType> nestedList;
|
||||||
if (componentTypes.containsKey(componentType.getCategory())) {
|
if (componentTypes.containsKey(componentType.getCategory())) {
|
||||||
nestedList = componentTypes.get(componentType.getCategory());
|
nestedList = componentTypes.get(componentType.getCategory());
|
||||||
} else {
|
} else {
|
||||||
nestedList = new ArrayList<ComponentType>();
|
nestedList = new ArrayList<ComponentType>();
|
||||||
componentTypes.put(componentType.getCategory(), nestedList);
|
componentTypes.put(componentType.getCategory(), nestedList);
|
||||||
}
|
}
|
||||||
nestedList.add(componentType);
|
nestedList.add(componentType);
|
||||||
|
|
||||||
if (LOG_COMPONENT_TYPE_DETAILS) {
|
if (LOG_COMPONENT_TYPE_DETAILS) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||||
snapshot.put("name", componentType.getName());
|
snapshot.put("name", componentType.getName());
|
||||||
snapshot.put("description", componentType.getDescription());
|
snapshot.put("description", componentType.getDescription());
|
||||||
snapshot.put("creationMethod", componentType.getCreationMethod());
|
snapshot.put("creationMethod", componentType.getCreationMethod());
|
||||||
snapshot.put("category", componentType.getCategory());
|
snapshot.put("category", componentType.getCategory());
|
||||||
List<PropertyWrapper> props = propertyCache.get(clazz.getName());
|
List<PropertyWrapper> props = propertyCache.get(clazz.getName());
|
||||||
snapshot.put("properties", props != null ? props.stream()
|
snapshot.put("properties", props != null ? props.stream()
|
||||||
.map(p -> {
|
.map(p -> {
|
||||||
Map<String, Object> prop = new LinkedHashMap<>();
|
Map<String, Object> prop = new LinkedHashMap<>();
|
||||||
prop.put("name", p.getName());
|
prop.put("name", p.getName());
|
||||||
prop.put("type", formatPropertyType(p.getType()));
|
prop.put("type", formatPropertyType(p.getType()));
|
||||||
List<String> possibleValues = getPossibleValues(p.getType());
|
List<String> possibleValues = getPossibleValues(p.getType());
|
||||||
if (possibleValues != null) {
|
if (possibleValues != null) {
|
||||||
prop.put("possibleValues", possibleValues);
|
prop.put("possibleValues", possibleValues);
|
||||||
}
|
}
|
||||||
return prop;
|
return prop;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList()) : Collections.emptyList());
|
.collect(Collectors.toList()) : Collections.emptyList());
|
||||||
componentSnapshots.add(snapshot);
|
componentSnapshots.add(snapshot);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Failed to log component type details for " + clazz.getName(), 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.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -3133,16 +3132,8 @@ public class Presenter implements IPlugInPort {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<INetlistAnalyzer> getNetlistAnalyzers() {
|
public List<INetlistAnalyzer> getNetlistAnalyzers() {
|
||||||
Set<Class<?>> classes;
|
|
||||||
try {
|
try {
|
||||||
classes = Utils.getClasses("org.diylc.netlist");
|
List<INetlistAnalyzer> result = ReflectionUtils.getIndexedInstances(INetlistAnalyzer.class);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.sort(new Comparator<INetlistAnalyzer>() {
|
result.sort(new Comparator<INetlistAnalyzer>() {
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import java.util.List;
|
|||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import org.diylc.core.Project;
|
import org.diylc.core.Project;
|
||||||
|
import org.diylc.core.annotations.Indexed;
|
||||||
|
|
||||||
|
@Indexed
|
||||||
public interface IOldFileParser {
|
public interface IOldFileParser {
|
||||||
|
|
||||||
boolean canParse(String version);
|
boolean canParse(String version);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -41,7 +40,6 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.diylc.appframework.miscutils.Utils;
|
|
||||||
import org.diylc.appframework.simplemq.MessageDispatcher;
|
import org.diylc.appframework.simplemq.MessageDispatcher;
|
||||||
import org.diylc.appframework.update.VersionNumber;
|
import org.diylc.appframework.update.VersionNumber;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
@@ -59,6 +57,7 @@ import org.diylc.core.IDIYComponent;
|
|||||||
import org.diylc.core.Project;
|
import org.diylc.core.Project;
|
||||||
import org.diylc.presenter.Presenter;
|
import org.diylc.presenter.Presenter;
|
||||||
import org.diylc.test.DIYTest;
|
import org.diylc.test.DIYTest;
|
||||||
|
import org.diylc.utils.ReflectionUtils;
|
||||||
|
|
||||||
public class ProjectFileManager {
|
public class ProjectFileManager {
|
||||||
|
|
||||||
@@ -144,17 +143,10 @@ public class ProjectFileManager {
|
|||||||
|
|
||||||
public List<IOldFileParser> getParsers() {
|
public List<IOldFileParser> getParsers() {
|
||||||
if (parsers == null) {
|
if (parsers == null) {
|
||||||
parsers = new ArrayList<IOldFileParser>();
|
|
||||||
Set<Class<?>> componentTypeClasses = null;
|
|
||||||
try {
|
try {
|
||||||
componentTypeClasses = Utils.getClasses("org.diylc.serialization");
|
parsers = ReflectionUtils.getIndexedInstances(IOldFileParser.class);
|
||||||
for (Class<?> clazz : componentTypeClasses) {
|
|
||||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IOldFileParser.class.isAssignableFrom(clazz)) {
|
|
||||||
IOldFileParser instance = (IOldFileParser) clazz.getDeclaredConstructor().newInstance();
|
|
||||||
parsers.add(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
parsers = new ArrayList<IOldFileParser>();
|
||||||
LOG.error("Could not find old version parsers", e);
|
LOG.error("Could not find old version parsers", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,23 +21,30 @@
|
|||||||
*/
|
*/
|
||||||
package org.diylc.utils;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.diylc.appframework.miscutils.Utils;
|
|
||||||
|
|
||||||
import org.diylc.core.annotations.IAutoCreator;
|
import org.diylc.core.annotations.IAutoCreator;
|
||||||
|
import org.diylc.core.annotations.Indexed;
|
||||||
import org.diylc.netlist.IGuitarDiagramAnalyzer;
|
import org.diylc.netlist.IGuitarDiagramAnalyzer;
|
||||||
import org.diylc.netlist.INetlistParser;
|
import org.diylc.netlist.INetlistParser;
|
||||||
import org.diylc.presenter.Presenter;
|
|
||||||
|
|
||||||
public class ReflectionUtils {
|
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
|
* {@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;
|
private static List<IAutoCreator> autoCreators;
|
||||||
|
|
||||||
public static List<IAutoCreator> getAutoCreators() {
|
/**
|
||||||
if (autoCreators == null) {
|
* Returns the concrete implementations of an {@link Indexed} type, as listed at build time by
|
||||||
autoCreators = new ArrayList<IAutoCreator>();
|
* {@code diylc-processor}. Classes are loaded but not initialized.
|
||||||
Set<Class<?>> classes = null;
|
*/
|
||||||
try {
|
public static <T> List<Class<? extends T>> getIndexedClasses(Class<T> type) {
|
||||||
classes = Utils.getClasses("org.diylc.components.autocreate");
|
ClassLoader classLoader = ReflectionUtils.class.getClassLoader();
|
||||||
for (Class<?> clazz : classes) {
|
// each module jar carries its own index file for the same type
|
||||||
if (IAutoCreator.class.isAssignableFrom(clazz)) {
|
Set<String> classNames = new LinkedHashSet<String>();
|
||||||
autoCreators.add((IAutoCreator) clazz.getDeclaredConstructor().newInstance());
|
try {
|
||||||
LOG.debug("Loaded auto-creator: " + clazz.getName());
|
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) {
|
} catch (Exception e) {
|
||||||
|
autoCreators = new ArrayList<IAutoCreator>();
|
||||||
LOG.error("Error loading auto-creator types", e);
|
LOG.error("Error loading auto-creator types", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,16 +119,8 @@ public class ReflectionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<INetlistParser> getNetlistParserDefinitions() {
|
public static List<INetlistParser> getNetlistParserDefinitions() {
|
||||||
Set<Class<?>> classes;
|
|
||||||
try {
|
try {
|
||||||
classes = Utils.getClasses("org.diylc.netlist");
|
List<INetlistParser> result = getIndexedInstances(INetlistParser.class);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(result, new Comparator<INetlistParser>() {
|
Collections.sort(result, new Comparator<INetlistParser>() {
|
||||||
|
|
||||||
@@ -92,16 +138,7 @@ public class ReflectionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IGuitarDiagramAnalyzer getGuitarDiagramAnalyzer() throws Exception {
|
public static IGuitarDiagramAnalyzer getGuitarDiagramAnalyzer() throws Exception {
|
||||||
Set<Class<?>> classes;
|
List<IGuitarDiagramAnalyzer> result = getIndexedInstances(IGuitarDiagramAnalyzer.class);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -611,7 +611,11 @@
|
|||||||
</org.diylc.appframework.update.Change>
|
</org.diylc.appframework.update.Change>
|
||||||
<org.diylc.appframework.update.Change>
|
<org.diylc.appframework.update.Change>
|
||||||
<changeType>IMPROVEMENT</changeType>
|
<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>
|
||||||
<org.diylc.appframework.update.Change>
|
<org.diylc.appframework.update.Change>
|
||||||
<changeType>BUG_FIX</changeType>
|
<changeType>BUG_FIX</changeType>
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Internal module dependencies -->
|
<!-- Internal module dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.diylc</groupId>
|
||||||
|
<artifactId>diylc-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.diylc</groupId>
|
<groupId>org.diylc</groupId>
|
||||||
<artifactId>diylc-core</artifactId>
|
<artifactId>diylc-core</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.diylc</groupId>
|
||||||
|
<artifactId>diylc-parent</artifactId>
|
||||||
|
<version>6.8.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>diylc-processor</artifactId>
|
||||||
|
<name>DIYLC Annotation Processor</name>
|
||||||
|
<description>Build-time indexer that lists the implementations of @Indexed types under META-INF/services</description>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!-- the processor cannot run while it is itself being compiled -->
|
||||||
|
<proc>none</proc>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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.processor;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ElementKind;
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.type.DeclaredType;
|
||||||
|
import javax.lang.model.type.TypeKind;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists every concrete class of the module being compiled under
|
||||||
|
* {@code META-INF/services/<type>} for each of its supertypes that carries
|
||||||
|
* {@code org.diylc.core.annotations.Indexed}. The application reads these files at startup instead
|
||||||
|
* of scanning the classpath.
|
||||||
|
*
|
||||||
|
* @author Branislav Stojkovic
|
||||||
|
*/
|
||||||
|
@SupportedAnnotationTypes("*")
|
||||||
|
public class ServiceIndexProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
private static final String INDEXED_ANNOTATION = "org.diylc.core.annotations.Indexed";
|
||||||
|
private static final String SERVICES_DIR = "META-INF/services/";
|
||||||
|
|
||||||
|
private final Map<String, Set<String>> index = new TreeMap<String, Set<String>>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SourceVersion getSupportedSourceVersion() {
|
||||||
|
return SourceVersion.latestSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||||
|
if (roundEnv.processingOver()) {
|
||||||
|
writeIndex();
|
||||||
|
} else {
|
||||||
|
for (Element element : roundEnv.getRootElements()) {
|
||||||
|
collect(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collect(Element element) {
|
||||||
|
if (!(element instanceof TypeElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeElement type = (TypeElement) element;
|
||||||
|
if (isInstantiable(type)) {
|
||||||
|
String className = processingEnv.getElementUtils().getBinaryName(type).toString();
|
||||||
|
for (String service : findIndexedSupertypes(type)) {
|
||||||
|
index.computeIfAbsent(service, k -> new TreeSet<String>()).add(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Element enclosed : type.getEnclosedElements()) {
|
||||||
|
if (enclosed.getModifiers().contains(Modifier.STATIC)) {
|
||||||
|
collect(enclosed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInstantiable(TypeElement type) {
|
||||||
|
return type.getKind() == ElementKind.CLASS && !type.getModifiers().contains(Modifier.ABSTRACT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> findIndexedSupertypes(TypeElement type) {
|
||||||
|
Set<String> result = new HashSet<String>();
|
||||||
|
Set<String> visited = new HashSet<String>();
|
||||||
|
Deque<TypeMirror> pending = new ArrayDeque<TypeMirror>();
|
||||||
|
pending.add(type.asType());
|
||||||
|
while (!pending.isEmpty()) {
|
||||||
|
for (TypeMirror supertype : processingEnv.getTypeUtils().directSupertypes(pending.poll())) {
|
||||||
|
if (supertype.getKind() != TypeKind.DECLARED) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TypeElement supertypeElement = (TypeElement) ((DeclaredType) supertype).asElement();
|
||||||
|
String name = processingEnv.getElementUtils().getBinaryName(supertypeElement).toString();
|
||||||
|
if (!visited.add(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isIndexed(supertypeElement)) {
|
||||||
|
result.add(name);
|
||||||
|
}
|
||||||
|
pending.add(supertype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isIndexed(TypeElement type) {
|
||||||
|
for (AnnotationMirror annotation : type.getAnnotationMirrors()) {
|
||||||
|
TypeElement annotationType = (TypeElement) annotation.getAnnotationType().asElement();
|
||||||
|
if (annotationType.getQualifiedName().contentEquals(INDEXED_ANNOTATION)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeIndex() {
|
||||||
|
for (Map.Entry<String, Set<String>> entry : index.entrySet()) {
|
||||||
|
String path = SERVICES_DIR + entry.getKey();
|
||||||
|
Set<String> classNames = new TreeSet<String>(entry.getValue());
|
||||||
|
// an incremental build (e.g. in the IDE) compiles only the changed classes, so carry over
|
||||||
|
// the entries from the previous build that still qualify
|
||||||
|
classNames.addAll(readPreviousEntries(path, entry.getKey()));
|
||||||
|
try {
|
||||||
|
FileObject file =
|
||||||
|
processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", path);
|
||||||
|
try (Writer writer =
|
||||||
|
new OutputStreamWriter(file.openOutputStream(), StandardCharsets.UTF_8)) {
|
||||||
|
for (String className : classNames) {
|
||||||
|
writer.write(className);
|
||||||
|
writer.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
|
||||||
|
"Could not write " + path + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> readPreviousEntries(String path, String service) {
|
||||||
|
Set<String> result = new TreeSet<String>();
|
||||||
|
try {
|
||||||
|
FileObject file =
|
||||||
|
processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", path);
|
||||||
|
try (BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(file.openInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line.isEmpty() && stillQualifies(line, service)) {
|
||||||
|
result.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// no previous build output
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean stillQualifies(String className, String service) {
|
||||||
|
TypeElement type = processingEnv.getElementUtils().getTypeElement(className.replace('$', '.'));
|
||||||
|
return type != null && isInstantiable(type) && findIndexedSupertypes(type).contains(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
org.diylc.processor.ServiceIndexProcessor
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Internal module dependencies -->
|
<!-- Internal module dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.diylc</groupId>
|
||||||
|
<artifactId>diylc-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.diylc</groupId>
|
<groupId>org.diylc</groupId>
|
||||||
<artifactId>diylc-core</artifactId>
|
<artifactId>diylc-core</artifactId>
|
||||||
|
|||||||
+21
-1
@@ -90,6 +90,8 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
private List<String> blocks;
|
private List<String> blocks;
|
||||||
|
|
||||||
private final Map<String, Component> categoryPanels = new HashMap<String, Component>();
|
private final Map<String, Component> categoryPanels = new HashMap<String, Component>();
|
||||||
|
private final Map<Component, List<ComponentType>> unpopulatedPanels =
|
||||||
|
new HashMap<Component, List<ComponentType>>();
|
||||||
private boolean refreshing = false;
|
private boolean refreshing = false;
|
||||||
|
|
||||||
public ComponentTabbedPane(IPlugInPort plugInPort, ISwingUI swingUI) {
|
public ComponentTabbedPane(IPlugInPort plugInPort, ISwingUI swingUI) {
|
||||||
@@ -111,6 +113,7 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
if (lastSelectedTab >= 0 && lastSelectedTab < getTabCount()) {
|
if (lastSelectedTab >= 0 && lastSelectedTab < getTabCount()) {
|
||||||
setSelectedIndex(lastSelectedTab);
|
setSelectedIndex(lastSelectedTab);
|
||||||
}
|
}
|
||||||
|
populateTab(getSelectedComponent());
|
||||||
|
|
||||||
addChangeListener(new ChangeListener() {
|
addChangeListener(new ChangeListener() {
|
||||||
|
|
||||||
@@ -119,6 +122,7 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
if (refreshing) {
|
if (refreshing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
populateTab(getSelectedComponent());
|
||||||
ConfigurationManager.getInstance().writeValue(LAST_SELECTED_TAB, getSelectedIndex());
|
ConfigurationManager.getInstance().writeValue(LAST_SELECTED_TAB, getSelectedIndex());
|
||||||
ComponentTabbedPane.this.plugInPort.setNewComponentTypeSlot(null, null, null, false);
|
ComponentTabbedPane.this.plugInPort.setNewComponentTypeSlot(null, null, null, false);
|
||||||
// Refresh recent components if needed
|
// Refresh recent components if needed
|
||||||
@@ -157,6 +161,11 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
}
|
}
|
||||||
addTab(category, panel);
|
addTab(category, panel);
|
||||||
}
|
}
|
||||||
|
// the tabbed pane takes its height from the tallest tab, so keep one category populated
|
||||||
|
// even while a sparse tab like Favorites is selected
|
||||||
|
if (getTabCount() > 3) {
|
||||||
|
populateTab(getComponentAt(3));
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedTitle != null) {
|
if (selectedTitle != null) {
|
||||||
for (int i = 0; i < getTabCount(); i++) {
|
for (int i = 0; i < getTabCount(); i++) {
|
||||||
@@ -175,6 +184,7 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
} else if (getTabCount() > 0) {
|
} else if (getTabCount() > 0) {
|
||||||
setSelectedIndex(0);
|
setSelectedIndex(0);
|
||||||
}
|
}
|
||||||
|
populateTab(getSelectedComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -238,11 +248,21 @@ class ComponentTabbedPane extends JTabbedPane {
|
|||||||
JPanel panel = new JPanel(new BorderLayout());
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
panel.setOpaque(false);
|
panel.setOpaque(false);
|
||||||
panel.add(createComponentPanel(componentTypes), BorderLayout.CENTER);
|
// buttons are added by populateTab() when the tab is first shown, as creating a button draws
|
||||||
|
// its component icon
|
||||||
|
unpopulatedPanels.put(panel, componentTypes);
|
||||||
|
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void populateTab(Component panel) {
|
||||||
|
List<ComponentType> componentTypes = unpopulatedPanels.remove(panel);
|
||||||
|
if (componentTypes != null) {
|
||||||
|
((JPanel) panel).add(createComponentPanel(componentTypes), BorderLayout.CENTER);
|
||||||
|
panel.revalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Container getRecentToolbar() {
|
public Container getRecentToolbar() {
|
||||||
if (recentToolbar == null) {
|
if (recentToolbar == null) {
|
||||||
recentToolbar = new Container();
|
recentToolbar = new Container();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<description>A free, open-source, cross-platform tool for drawing circuit layouts</description>
|
<description>A free, open-source, cross-platform tool for drawing circuit layouts</description>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
<module>diylc-processor</module>
|
||||||
<module>diylc-core</module>
|
<module>diylc-core</module>
|
||||||
<module>diylc-library</module>
|
<module>diylc-library</module>
|
||||||
<module>diylc-swing</module>
|
<module>diylc-swing</module>
|
||||||
@@ -31,6 +32,13 @@
|
|||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Internal module dependencies -->
|
<!-- Internal module dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.diylc</groupId>
|
||||||
|
<artifactId>diylc-processor</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.diylc</groupId>
|
<groupId>org.diylc</groupId>
|
||||||
<artifactId>diylc-core</artifactId>
|
<artifactId>diylc-core</artifactId>
|
||||||
@@ -94,7 +102,20 @@
|
|||||||
<source>${maven.compiler.source}</source>
|
<source>${maven.compiler.source}</source>
|
||||||
<target>${maven.compiler.target}</target>
|
<target>${maven.compiler.target}</target>
|
||||||
<encoding>${project.build.sourceEncoding}</encoding>
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
|
<!-- writes the META-INF/services class index; see org.diylc.core.annotations.Indexed -->
|
||||||
|
<annotationProcessors>
|
||||||
|
<annotationProcessor>org.diylc.processor.ServiceIndexProcessor</annotationProcessor>
|
||||||
|
</annotationProcessors>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<!-- test fixtures must not end up in the index -->
|
||||||
|
<id>default-testCompile</id>
|
||||||
|
<configuration>
|
||||||
|
<proc>none</proc>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user