From 1b1741d826deb033a9f89fb29c70c2a3efd10bd4 Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Wed, 20 Jul 2022 16:24:22 +0200 Subject: [PATCH 1/3] diagrams: Added attribute support for icon annotation of reactors --- .../diagram/synthesis/util/ReactorIcons.java | 6 +++++- org.lflang/src/org/lflang/AttributeUtils.java | 21 ++++++++++++++----- .../org/lflang/validation/AttributeSpec.java | 4 ++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java index 5412879513..64904d77f3 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java @@ -47,6 +47,7 @@ import org.eclipse.xtext.xbase.lib.Extension; import org.eclipse.xtext.xbase.lib.StringExtensions; import org.lflang.ASTUtils; +import org.lflang.AttributeUtils; import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.lf.ReactorDecl; @@ -101,7 +102,10 @@ public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boole private URL locateIcon(EObject eobj) { URL location = null; - String iconPath = ASTUtils.findAnnotationInComments(eobj, "@icon"); + String iconPath = AttributeUtils.findAttributeByName(eobj, "icon"); + if (iconPath == null) { // Fallback to old syntax (in comment) + iconPath = ASTUtils.findAnnotationInComments(eobj, "@icon"); + } if (!StringExtensions.isNullOrEmpty(iconPath)) { // Check if path is URL try { diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index 9c67b06754..aa57cd18dd 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -41,8 +41,10 @@ /** * A helper class for processing attributes in the AST. + * * @author{Shaokai Lin } * @author{Clément Fournier, TU Dresden, INSA Rennes} + * @author{Alexander Schulz-Rosengarten } */ public class AttributeUtils { @@ -74,19 +76,28 @@ public static List getAttributes(EObject node) { } /** - * Return the value of the {@code @label} attribute if - * present, otherwise return null. + * Return the value of the attribute with the given name + * if present, otherwise return null. * * @throws IllegalArgumentException If the node cannot have attributes */ - public static String findLabelAttribute(EObject node) { + public static String findAttributeByName(EObject node, String name) { List attrs = getAttributes(node); return attrs.stream() - .filter(it -> it.getAttrName().equals("label")) + .filter(it -> it.getAttrName().equalsIgnoreCase(name)) // case-insensitive search (more user-friendly) .map(it -> it.getAttrParms().get(0).getValue().getStr()) .findFirst() .orElse(null); - + } + + /** + * Return the value of the {@code @label} attribute if + * present, otherwise return null. + * + * @throws IllegalArgumentException If the node cannot have attributes + */ + public static String findLabelAttribute(EObject node) { + return findAttributeByName(node, "label"); } /** diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 07c70af72a..12f863a40a 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -185,5 +185,9 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) )); + // @icon("value") + ATTRIBUTE_SPECS_BY_NAME.put("icon", new AttributeSpec( + List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) + )); } } From 146049c6b228735ceeae04d339d3ed750b242260 Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Wed, 20 Jul 2022 16:25:02 +0200 Subject: [PATCH 2/3] diagrams: Improved error reporting for reactor icons --- .../lflang/diagram/synthesis/util/ReactorIcons.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java index 64904d77f3..bf6fcb8510 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java @@ -25,6 +25,8 @@ package org.lflang.diagram.synthesis.util; import com.google.inject.Inject; + +import de.cau.cs.kieler.klighd.krendering.Colors; import de.cau.cs.kieler.klighd.krendering.KContainerRendering; import de.cau.cs.kieler.klighd.krendering.KGridPlacementData; import de.cau.cs.kieler.klighd.krendering.KRectangle; @@ -96,6 +98,11 @@ public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boole _kRenderingExtensions.TOP, 0, 0.5f), _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, 0, data.width, data.height); + } else { + var errorText = _kContainerRenderingExtensions.addText(rendering, "Icon not found!"); + _kRenderingExtensions.setForeground(errorText, Colors.RED); + _kRenderingExtensions.setFontBold(errorText, true); + _kRenderingExtensions.setSurroundingSpaceGrid(errorText, 8, 0); } } } @@ -187,7 +194,8 @@ private ImageData loadImage(final URL url) { } } } catch (IOException ex) { - throw Exceptions.sneakyThrow(ex); + ex.printStackTrace(); + return null; } } From bac0c0cdcae2210d1f996c3898189a0cb16d0bf4 Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Thu, 18 Aug 2022 10:45:35 +0200 Subject: [PATCH 3/3] diagrams: Improved error reporting for icon paths. --- .../diagram/synthesis/util/ReactorIcons.java | 182 +++++++----------- org.lflang/src/org/lflang/util/FileUtil.java | 53 +++++ .../org/lflang/validation/LFValidator.java | 33 ++++ 3 files changed, 151 insertions(+), 117 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java index bf6fcb8510..71f30e491b 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java @@ -24,34 +24,27 @@ ***************/ package org.lflang.diagram.synthesis.util; -import com.google.inject.Inject; - -import de.cau.cs.kieler.klighd.krendering.Colors; -import de.cau.cs.kieler.klighd.krendering.KContainerRendering; -import de.cau.cs.kieler.klighd.krendering.KGridPlacementData; -import de.cau.cs.kieler.klighd.krendering.KRectangle; -import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; -import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; -import java.io.File; -import java.io.IOException; import java.io.InputStream; -import java.lang.ref.SoftReference; -import java.net.URL; import java.util.HashMap; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.EObject; + import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; -import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.Extension; -import org.eclipse.xtext.xbase.lib.StringExtensions; import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.lf.ReactorDecl; +import org.lflang.util.FileUtil; + +import com.google.inject.Inject; + +import de.cau.cs.kieler.klighd.krendering.Colors; +import de.cau.cs.kieler.klighd.krendering.KContainerRendering; +import de.cau.cs.kieler.klighd.krendering.KGridPlacementData; +import de.cau.cs.kieler.klighd.krendering.KRectangle; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; +import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; /** * Utility class to handle icons for reactors in Lingua Franca diagrams. @@ -66,135 +59,90 @@ public class ReactorIcons extends AbstractSynthesisExtensions { private static final ImageLoader LOADER = new ImageLoader(); - // memory-sensitive cache - private static final HashMap> CACHE = new HashMap<>(); + // Image cache during synthesis + private final HashMap cache = new HashMap<>(); + // Error message + private String error = null; public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boolean collapsed) { if (!collapsed) { return; } - URL iconLocation = locateIcon(reactor); - if (iconLocation != null) { - ImageData data = loadImage(iconLocation); - if (data != null) { - KRectangle figure = _kContainerRenderingExtensions.addRectangle(rendering); - _kRenderingExtensions.setInvisible(figure, true); - KGridPlacementData figurePlacement = _kRenderingExtensions.setGridPlacementData(figure, data.width, data.height); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - figurePlacement, - _kRenderingExtensions.LEFT, 3, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 3, 0, - _kRenderingExtensions.BOTTOM, 3, 0); - - KRectangle icon = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(icon, true); - _kContainerRenderingExtensions.addImage(icon, data); - _kRenderingExtensions.setPointPlacementData(icon, - _kRenderingExtensions.createKPosition( - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0.5f), - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, - 0, data.width, data.height); - } else { - var errorText = _kContainerRenderingExtensions.addText(rendering, "Icon not found!"); - _kRenderingExtensions.setForeground(errorText, Colors.RED); - _kRenderingExtensions.setFontBold(errorText, true); - _kRenderingExtensions.setSurroundingSpaceGrid(errorText, 8, 0); - } - } - } - - private URL locateIcon(EObject eobj) { - URL location = null; - String iconPath = AttributeUtils.findAttributeByName(eobj, "icon"); + + // Reset error + error = null; + + // Get annotation + String iconPath = AttributeUtils.findAttributeByName(reactor, "icon"); if (iconPath == null) { // Fallback to old syntax (in comment) - iconPath = ASTUtils.findAnnotationInComments(eobj, "@icon"); + iconPath = ASTUtils.findAnnotationInComments(reactor, "@icon"); } - if (!StringExtensions.isNullOrEmpty(iconPath)) { - // Check if path is URL - try { - return new URL(iconPath); - } catch (Exception e) { - // nothing - } - // Check if path exists as is - File path = new File(iconPath); - if (path.exists()) { - try { - return path.toURI().toURL(); - } catch (Exception e) { - // nothing - } - } - // Check if path is relative to LF file - URI eURI = eobj.eResource() != null ? eobj.eResource().getURI() : null; - if (eURI != null) { - java.net.URI sourceURI = null; - try { - if (eURI.isFile()) { - sourceURI = new java.net.URI(eURI.toString()); - sourceURI = new java.net.URI(sourceURI.getScheme(), null, - sourceURI.getPath().substring(0, sourceURI.getPath().lastIndexOf("/")), null); - } else if (eURI.isPlatformResource()) { - IResource iFile = ResourcesPlugin.getWorkspace().getRoot().findMember(eURI.toPlatformString(true)); - sourceURI = iFile != null ? iFile.getRawLocation().toFile().getParentFile().toURI() : null; - } else if (eURI.isPlatformPlugin()) { - // TODO support loading from plugin bundles? - } - } catch (Exception e) { - // nothing - } - if (sourceURI != null) { - try { - location = sourceURI.resolve(path.toString()).toURL(); - } catch (Exception e) { - // nothing - } + if (iconPath != null && !iconPath.isEmpty()) { + var iconLocation = FileUtil.locateFile(iconPath, reactor.eResource()); + if (iconLocation == null) { + error = "Cannot find given icon file."; + } else { + ImageData data = loadImage(iconLocation); + if (data != null) { + KRectangle figure = _kContainerRenderingExtensions.addRectangle(rendering); + _kRenderingExtensions.setInvisible(figure, true); + KGridPlacementData figurePlacement = _kRenderingExtensions.setGridPlacementData(figure, data.width, data.height); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + figurePlacement, + _kRenderingExtensions.LEFT, 3, 0, + _kRenderingExtensions.TOP, 0, 0), + _kRenderingExtensions.RIGHT, 3, 0, + _kRenderingExtensions.BOTTOM, 3, 0); + KRectangle icon = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(icon, true); + _kContainerRenderingExtensions.addImage(icon, data); + _kRenderingExtensions.setPointPlacementData(icon, + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.LEFT, 0, 0.5f, + _kRenderingExtensions.TOP, 0, 0.5f), + _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, + 0, data.width, data.height); + } + if (error != null) { + var errorText = _kContainerRenderingExtensions.addText(rendering, "Icon not found!\n"+error); + _kRenderingExtensions.setForeground(errorText, Colors.RED); + _kRenderingExtensions.setFontBold(errorText, true); + _kRenderingExtensions.setSurroundingSpaceGrid(errorText, 8, 0); } } - // TODO more variants based on package and library system in LF } - return location; } - private ImageData loadImage(final URL url) { + private ImageData loadImage(final java.net.URI uri) { try { - synchronized (CACHE) { - if (CACHE.containsKey(url)) { - ImageData img = CACHE.get(url).get(); - if (img != null) { - return img; - } else { - CACHE.remove(url); - } - } + if (cache.containsKey(uri)) { + return cache.get(uri); } synchronized (LOADER) { InputStream inStream = null; try { - inStream = url.openStream(); - // TODO check for memory leak !!! + inStream = uri.toURL().openStream(); ImageData[] data = LOADER.load(inStream); if (data != null && data.length > 0) { ImageData img = data[0]; - synchronized (CACHE) { - CACHE.put(url, new SoftReference(img)); - } + cache.put(uri, img); return img; + } else { + error = "Could not load icon image."; + return null; } - return null; } finally { if (inStream != null) { inStream.close(); } } } - } catch (IOException ex) { + } catch (Exception ex) { ex.printStackTrace(); + error = "Could not load icon image."; return null; } } diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index 9f03dff65c..e82249bd8f 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -104,6 +104,59 @@ public static IPath toIPath(URI uri) throws IOException { public static String toUnixString(Path path) { return path.toString().replace('\\', '/'); } + + /** + * Parse the string as file location and return it as URI. + * Supports URIs, plain file paths, and paths relative to a model. + * + * @param path the file location as string. + * @param resource the model resource this file should be resolved relatively. May be null. + * @return the (Java) URI or null if no file can be located. + */ + public static java.net.URI locateFile(String path, Resource resource) { + // Check if path is URL + try { + var uri = new java.net.URI(path); + if(uri.getScheme() != null) { // check if path was meant to be a URI + return uri; + } + } catch (Exception e) { + // nothing + } + // Check if path exists as it is + File file = new File(path); + if (file.exists()) { + try { + return file.toURI(); + } catch (Exception e) { + // nothing + } + } + // Check if path is relative to LF file + if (resource != null) { + URI eURI = resource.getURI(); + if (eURI != null) { + java.net.URI sourceURI = null; + try { + if (eURI.isFile()) { + sourceURI = new java.net.URI(eURI.toString()); + sourceURI = new java.net.URI(sourceURI.getScheme(), null, + sourceURI.getPath().substring(0, sourceURI.getPath().lastIndexOf("/")), null); + } else if (eURI.isPlatformResource()) { + IResource iFile = ResourcesPlugin.getWorkspace().getRoot().findMember(eURI.toPlatformString(true)); + sourceURI = iFile != null ? iFile.getRawLocation().toFile().getParentFile().toURI() : null; + } + if (sourceURI != null) { + return sourceURI.resolve(path.toString()); + } + } catch (Exception e) { + // nothing + } + } + } + // fail + return null; + } /** * Recursively copies the contents of the given 'src' diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index d14b792ce1..8e13948bc8 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -34,6 +34,7 @@ import static org.lflang.ASTUtils.toDefinition; import static org.lflang.ASTUtils.toOriginalText; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -57,6 +58,7 @@ import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; import org.lflang.ASTUtils; +import org.lflang.AttributeUtils; import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; @@ -1253,6 +1255,37 @@ public void checkWidthSpec(WidthSpec widthSpec) { } } } + + @Check(CheckType.FAST) + public void checkReactorIconAttribute(Reactor reactor) { + var attrs = AttributeUtils.getAttributes(reactor); + var iconAttr = attrs.stream() + .filter(it -> it.getAttrName().equalsIgnoreCase("icon")) + .findFirst() + .orElse(null); + if (iconAttr != null) { + var path = iconAttr.getAttrParms().get(0).getValue().getStr(); + + // Check file extension + var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg"); + var extensionStrart = path.lastIndexOf("."); + var extension = extensionStrart != -1 ? path.substring(extensionStrart + 1) : ""; + if (!validExtensions.contains(extension.toLowerCase())) { + warning("File extension '" + extension + "' is not supported. Provide any of: " + String.join(", ", validExtensions), + iconAttr.getAttrParms().get(0), Literals.ATTR_PARM__VALUE); + return; + } + + // Check file location + var iconLocation = FileUtil.locateFile(path, reactor.eResource()); + if (iconLocation == null) { + warning("Cannot locate icon file.", iconAttr.getAttrParms().get(0), Literals.ATTR_PARM__VALUE); + } + if (("file".equals(iconLocation.getScheme()) || iconLocation.getScheme() == null) && !(new File(iconLocation.getPath()).exists())) { + warning("Icon does not exist.", iconAttr.getAttrParms().get(0), Literals.ATTR_PARM__VALUE); + } + } + } @Check(CheckType.FAST) public void checkInitialMode(Reactor reactor) {