1- /*
2- Copyright (c) 2022, The University of California at Berkeley.
3-
4- Redistribution and use in source and binary forms, with or without modification,
5- are permitted provided that the following conditions are met:
6-
7- 1. Redistributions of source code must retain the above copyright notice,
8- this list of conditions and the following disclaimer.
9-
10- 2. Redistributions in binary form must reproduce the above copyright notice,
11- this list of conditions and the following disclaimer in the documentation
12- and/or other materials provided with the distribution.
13-
14- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24- */
25-
261package org .lflang ;
272
283import static org .lflang .ast .ASTUtils .factory ;
327import java .util .Map ;
338import java .util .Objects ;
349import org .eclipse .emf .ecore .EObject ;
35- import org .eclipse .xtext .nodemodel .ICompositeNode ;
36- import org .eclipse .xtext .nodemodel .util .NodeModelUtils ;
37- import org .eclipse .xtext .resource .XtextResource ;
3810import org .lflang .ast .ASTUtils ;
3911import org .lflang .lf .*;
4012import org .lflang .target .property .type .PlatformType ;
4618 * @author Shaokai Lin
4719 * @author Clément Fournier
4820 * @author Alexander Schulz-Rosengarten
21+ * @author Edward A. Lee
4922 */
5023public class AttributeUtils {
5124
5225 /**
53- * Return the attributes declared on the given node. Throws if the node does not support declaring
54- * attributes.
26+ * Return a list of attributes declared on the given node. An empty list is returned if the node
27+ * does not have any attributes.
5528 *
56- * @throws IllegalArgumentException If the node cannot have attributes
29+ * @param node The node to get the attributes from.
30+ * @throws IllegalArgumentException If the node cannot have attributes.
5731 */
58- public static List <Attribute > getAttributes (EObject node ) {
32+ public static List <Attribute > getAttributes (EObject node ) throws IllegalArgumentException {
5933 if (node instanceof Reactor ) {
6034 return ((Reactor ) node ).getAttributes ();
6135 } else if (node instanceof Reaction ) {
@@ -83,11 +57,15 @@ public static List<Attribute> getAttributes(EObject node) {
8357 }
8458
8559 /**
86- * Return the attribute with the given name if present, otherwise return null.
60+ * Return the first attribute with the given name if present, otherwise return null. If there are
61+ * multiple attributes with the same name, only the first one is returned.
8762 *
63+ * @param node The node to get the attribute from.
64+ * @param name The name of the attribute to get.
8865 * @throws IllegalArgumentException If the node cannot have attributes
8966 */
90- public static Attribute findAttributeByName (EObject node , String name ) {
67+ public static Attribute findAttributeByName (EObject node , String name )
68+ throws IllegalArgumentException {
9169 List <Attribute > attrs = getAttributes (node );
9270 return attrs .stream ()
9371 .filter (
@@ -99,11 +77,15 @@ public static Attribute findAttributeByName(EObject node, String name) {
9977 }
10078
10179 /**
102- * Return the attributes with the given name.
80+ * Return a list of attributes with the given name. An empty list is returned if the node does not
81+ * have any attributes with the given name.
10382 *
83+ * @param node The node to get the attributes from.
84+ * @param name The name of the attributes to get.
10485 * @throws IllegalArgumentException If the node cannot have attributes
10586 */
106- public static List <Attribute > findAttributesByName (EObject node , String name ) {
87+ public static List <Attribute > findAttributesByName (EObject node , String name )
88+ throws IllegalArgumentException {
10789 List <Attribute > attrs = getAttributes (node );
10890 return attrs .stream ()
10991 .filter (
@@ -113,119 +95,61 @@ public static List<Attribute> findAttributesByName(EObject node, String name) {
11395 .toList ();
11496 }
11597
116- public static List <Attribute > findAttributesByNameStartingWith (EObject node , String name ) {
117- List <Attribute > attrs = getAttributes (node );
118- return attrs .stream ()
119- .filter (
120- it -> it .getAttrName ().contains (name )) // case-insensitive search (more user-friendly)
121- .toList ();
122- }
123-
124- /**
125- * Return the first argument specified for the attribute.
126- *
127- * <p>This should be used if the attribute is expected to have a single argument. If there is no
128- * argument, null is returned.
129- */
130- public static String getFirstArgumentValue (Attribute attr ) {
131- if (attr == null || attr .getAttrParms ().isEmpty ()) {
132- return null ;
133- }
134- return StringUtil .removeQuotes (attr .getAttrParms ().get (0 ).getValue ());
135- }
136-
13798 /**
138- * Search for an attribute with the given name on the given AST node and return its first argument
139- * as a String.
99+ * Return the first argument specified for the first attribute with the given name. If there is no
100+ * attribute with the given name, return null. If the attribute has no arguments, or if the
101+ * arguments are not of a suitable form, return null. This ignores any argument name, if one is
102+ * given, and only returns arguments of the form of strings ("foo"), numbers (123), boolean values
103+ * (true, false), or the time values `forever` or `never`. Time values with units are not returned
104+ * (the return value will be null).
140105 *
141- * <p>This should only be used on attributes that are expected to have a single argument.
106+ * <p>This is a convenience method for common use cases where an attribute is specified with a
107+ * single argument of a suitable form. The validator should check that the arguments are of a
108+ * suitable form.
142109 *
143- * <p>Returns null if the attribute is not found or if it does not have any arguments.
110+ * @param node The node to get the attribute value from.
111+ * @param attrName The name of the attribute to get the value from.
144112 */
145113 public static String getAttributeValue (EObject node , String attrName ) {
146114 final var attr = findAttributeByName (node , attrName );
147- String value = getFirstArgumentValue (attr );
148- // Attribute annotations in comments are deprecated, but we still check for then for backwards
149- // compatibility
150- if (value == null ) {
151- return findAnnotationInComments (node , "@" + attrName );
152- }
153- return value ;
154- }
155-
156- /**
157- * Search for an attribute with the given name on the given AST node and return its first argument
158- * as a String.
159- *
160- * <p>This should only be used on attributes that are expected to have a single argument.
161- *
162- * <p>Returns null if the attribute is not found or if it does not have any arguments.
163- */
164- public static Map <String , String > getAttributeValues (EObject node , String attrName ) {
165- final List <Attribute > attrs = findAttributesByName (node , attrName );
166- HashMap <String , String > layoutOptions = new HashMap <>();
167- for (Attribute attribute : attrs ) {
168- layoutOptions .put (
169- StringUtil .removeQuotes (attribute .getAttrParms ().get (0 ).getValue ()),
170- StringUtil .removeQuotes (attribute .getAttrParms ().get (1 ).getValue ()));
115+ if (attr == null || attr .getAttrParms ().isEmpty ()) {
116+ return null ;
171117 }
172- return layoutOptions ;
173- }
174-
175- /**
176- * Retrieve a specific annotation in a comment associated with the given model element in the AST.
177- *
178- * <p>This will look for a comment. If one is found, it searches for the given annotation {@code
179- * key}. and extracts any string that follows the annotation marker.
180- *
181- * @param object the AST model element to search a comment for
182- * @param key the specific annotation key to be extracted
183- * @return {@code null} if no JavaDoc style comment was found or if it does not contain the given
184- * key. The string immediately following the annotation marker otherwise.
185- */
186- public static String findAnnotationInComments (EObject object , String key ) {
187- if (!(object .eResource () instanceof XtextResource )) return null ;
188- ICompositeNode node = NodeModelUtils .findActualNodeFor (object );
189- return ASTUtils .getPrecedingComments (node , n -> true )
190- .flatMap (String ::lines )
191- .filter (line -> line .contains (key ))
192- .map (String ::trim )
193- .map (it -> it .substring (it .indexOf (key ) + key .length ()))
194- .map (it -> it .endsWith ("*/" ) ? it .substring (0 , it .length () - "*/" .length ()) : it )
195- .findFirst ()
196- .orElse (null );
118+ return StringUtil .removeQuotes (attr .getAttrParms ().get (0 ).getValue ());
197119 }
198120
199121 /**
200- * Return the parameter of the given attribute with the given name.
122+ * Return the parameter with the given name for the specified attribute or null if no such
123+ * parameter is found.
201124 *
202- * <p>Returns null if no such parameter is found.
125+ * @param attribute The attribute to get the parameter from.
126+ * @param parameterName The name of the parameter to get.
203127 */
204- public static String getAttributeParameter (Attribute attribute , String parameterName ) {
128+ public static AttrParm getAttributeParameter (Attribute attribute , String parameterName ) {
205129 return (attribute == null )
206130 ? null
207131 : attribute .getAttrParms ().stream ()
208132 .filter (param -> Objects .equals (param .getName (), parameterName ))
209- .map (AttrParm ::getValue )
210- .map (StringUtil ::removeQuotes )
211133 .findFirst ()
212134 .orElse (null );
213135 }
214136
215137 /**
216- * Return the parameter of the given attribute with the given name and interpret it as a boolean.
138+ * Return true if there is a parameter of the given attribute with the given name whose value is
139+ * "true" (case-insensitive) and return false otherwise.
217140 *
218- * <p>Returns null if no such parameter is found.
141+ * @param attribute The attribute to get the parameter from.
142+ * @param parameterName The name of the parameter to get.
219143 */
220- public static Boolean getBooleanAttributeParameter (Attribute attribute , String parameterName ) {
144+ public static boolean getBooleanAttributeParameter (Attribute attribute , String parameterName ) {
221145 if (attribute == null || parameterName == null ) {
222- return null ;
146+ return false ;
223147 }
224148 final var param = getAttributeParameter (attribute , parameterName );
225- if (param == null ) {
226- return null ;
149+ if (param == null || param . getValue () == null ) {
150+ return false ;
227151 }
228- return param .equalsIgnoreCase ("true" );
152+ return param .getValue (). equalsIgnoreCase ("true" );
229153 }
230154
231155 /**
@@ -271,24 +195,39 @@ public static String getPortSide(EObject node) {
271195 }
272196
273197 /**
274- * Return the {@code layout} annotation for the given element or null if there is no such
275- * annotation.
198+ * Return the `@layout` annotations for the given element or null if there is no such annotation.
199+ * Layout annotations have the form: ```
200+ *
201+ * @layout(option="string", value="any") ``` For example, ```
202+ * @layout(option="port.side", value="WEST") ``` This will return all such annotations for the
203+ * specified node in the form of a map from the option name to the value.
276204 */
277205 public static Map <String , String > getLayoutOption (EObject node ) {
278- return getAttributeValues (node , "layout" );
206+ final List <Attribute > attrs = findAttributesByName (node , "layout" );
207+ HashMap <String , String > result = new HashMap <>();
208+ for (Attribute attribute : attrs ) {
209+ result .put (
210+ // FIXME: This assumes the parameters are in the correct order.
211+ StringUtil .removeQuotes (attribute .getAttrParms ().get (0 ).getValue ()),
212+ StringUtil .removeQuotes (attribute .getAttrParms ().get (1 ).getValue ()));
213+ }
214+ return result ;
279215 }
280216
281217 /**
282- * Return the {@code @enclave} attribute annotated on the given node.
218+ * Return a list of attributes with names containing the text "interface". An empty list is
219+ * returned if the node does not have any attributes with names starting with "interface".
283220 *
284- * <p>Returns null if there is no such attribute .
221+ * @param node The instantiation node to get the attributes from .
285222 */
286- public static Attribute getEnclaveAttribute (Instantiation node ) {
287- return findAttributeByName (node , "enclave" );
288- }
289-
290223 public static List <Attribute > getInterfaceAttributes (Instantiation node ) {
291- return findAttributesByNameStartingWith (node , "interface" );
224+ List <Attribute > attrs = getAttributes (node );
225+ return attrs .stream ()
226+ .filter (
227+ it ->
228+ it .getAttrName ()
229+ .contains ("interface" )) // case-insensitive search (more user-friendly)
230+ .toList ();
292231 }
293232
294233 public static Attribute getJoiningPolicy (Instantiation node ) {
@@ -319,7 +258,7 @@ public static Attribute getLinkAttribute(Connection node) {
319258
320259 /** Return true if the specified instance has an {@code @enclave} attribute. */
321260 public static boolean isEnclave (Instantiation node ) {
322- return getEnclaveAttribute (node ) != null ;
261+ return findAttributeByName (node , "enclave" ) != null ;
323262 }
324263
325264 /**
@@ -354,4 +293,31 @@ public static boolean isGrandmaster(Instantiation node) {
354293 public static Attribute getClockSyncAttr (Instantiation inst ) {
355294 return findAttributeByName (inst , "clock_sync" );
356295 }
296+
297+ /**
298+ * Return the value of the `@maxwait` attribute of the given node or TimeValue.ZERO if does not
299+ * have one.
300+ *
301+ * @param The AST node (Instantiation or Connection).
302+ */
303+ public static TimeValue getMaxWait (EObject node ) {
304+ final var attr = findAttributeByName (node , "maxwait" );
305+ if (attr != null ) {
306+ // The attribute is expected to have a single argument of type Time
307+ // or one of the literals "forever", "never", or "0".
308+ // The validator checks this.
309+ final var time = attr .getAttrParms ().get (0 ).getTime ();
310+ if (time == null ) {
311+ if (attr .getAttrParms ().get (0 ).getValue ().equals ("forever" )) {
312+ return TimeValue .MAX_VALUE ;
313+ } else if (attr .getAttrParms ().get (0 ).getValue ().equals ("never" )) {
314+ // Interpret "never" as 0.
315+ return TimeValue .ZERO ;
316+ }
317+ } else {
318+ return ASTUtils .toTimeValue (time );
319+ }
320+ }
321+ return TimeValue .ZERO ;
322+ }
357323}
0 commit comments