View Javadoc
1   package io.jawk.ext;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2026 MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
33  import io.jawk.backend.AVM;
34  import io.jawk.ext.annotations.JawkBeforeStart;
35  import io.jawk.ext.annotations.JawkFunction;
36  import io.jawk.jrt.IllegalAwkArgumentException;
37  import io.jawk.jrt.JRT;
38  import io.jawk.jrt.VariableManager;
39  import io.jawk.util.AwkSettings;
40  
41  /**
42   * Base class of various extensions.
43   * <p>
44   * Provides functionality common to most extensions,
45   * such as VM and JRT variable management, and convenience
46   * methods such as checkNumArgs() and toAwkString().
47   *
48   * @author Danny Daglas
49   */
50  public abstract class AbstractExtension implements JawkExtension {
51  
52  	private JRT jrt;
53  	private VariableManager vm;
54  	private AwkSettings settings;
55  	private Map<String, ExtensionFunction> annotatedFunctions;
56  	private List<Method> beforeStartMethods;
57  
58  	/** {@inheritDoc} */
59  	@Override
60  	public String getExtensionName() {
61  		return getClass().getSimpleName();
62  	}
63  
64  	/** {@inheritDoc} */
65  	@Override
66  	@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Extension needs direct access to runtime, VM and settings")
67  	public void init(VariableManager vmParam, JRT runtime, final AwkSettings conf) {
68  		this.vm = vmParam;
69  		this.jrt = runtime;
70  		this.settings = conf;
71  	}
72  
73  	/**
74  	 * Convert a Jawk variable to a Jawk string
75  	 * based on the value of the CONVFMT variable.
76  	 *
77  	 * @param obj The Jawk variable to convert to a Jawk string.
78  	 * @return A string representation of obj after CONVFMT
79  	 *         has been applied.
80  	 */
81  	protected final String toAwkString(Object obj) {
82  		return jrt.toAwkString(obj);
83  	}
84  
85  	/**
86  	 * Verifies that an exact number of arguments
87  	 * has been passed in by checking the length
88  	 * of the argument array.
89  	 *
90  	 * @param arr The arguments to check.
91  	 * @param expectedNum The expected number of arguments.
92  	 */
93  	protected static void checkNumArgs(Object[] arr, int expectedNum) {
94  		// some sanity checks on the arguments
95  		// (made into assertions so that
96  		// production code does not perform
97  		// these checks)
98  
99  		if (arr.length != expectedNum) {
100 			throw new IllegalAwkArgumentException("Expecting " + expectedNum + " arg(s), got " + arr.length);
101 		}
102 	}
103 
104 	/**
105 	 * <p>
106 	 * Getter for the field <code>jrt</code>.
107 	 * </p>
108 	 *
109 	 * @return the Runtime
110 	 */
111 	protected JRT getJrt() {
112 		return jrt;
113 	}
114 
115 	/**
116 	 * <p>
117 	 * Getter for the field <code>vm</code>.
118 	 * </p>
119 	 *
120 	 * @return the Variable Manager
121 	 */
122 	protected VariableManager getVm() {
123 		return vm;
124 	}
125 
126 	/**
127 	 * <p>
128 	 * Getter for the field <code>settings</code>.
129 	 * </p>
130 	 *
131 	 * @return the Settings
132 	 */
133 	protected AwkSettings getSettings() {
134 		return settings;
135 	}
136 
137 	private Map<String, ExtensionFunction> getAnnotatedFunctions() {
138 		if (annotatedFunctions == null) {
139 			annotatedFunctions = Collections.unmodifiableMap(scanAnnotatedFunctions());
140 		}
141 		return annotatedFunctions;
142 	}
143 
144 	private List<Method> getBeforeStartMethods() {
145 		if (beforeStartMethods == null) {
146 			beforeStartMethods = Collections.unmodifiableList(scanBeforeStartMethods());
147 		}
148 		return beforeStartMethods;
149 	}
150 
151 	private Map<String, ExtensionFunction> scanAnnotatedFunctions() {
152 		Map<String, ExtensionFunction> discovered = new LinkedHashMap<String, ExtensionFunction>();
153 		Class<? extends AbstractExtension> type = getClass();
154 		for (Method method : type.getMethods()) {
155 			JawkFunction function = method.getAnnotation(JawkFunction.class);
156 			if (function == null) {
157 				continue;
158 			}
159 			String keyword = function.value();
160 			ExtensionFunction existing = discovered.put(keyword, new ExtensionFunction(keyword, method));
161 			if (existing != null) {
162 				throw new IllegalStateException(
163 						"Duplicate @JawkFunction mapping for keyword '" + keyword + "' in " + type.getName());
164 			}
165 		}
166 		return discovered;
167 	}
168 
169 	private List<Method> scanBeforeStartMethods() {
170 		List<Method> discovered = new ArrayList<Method>();
171 		Class<? extends AbstractExtension> type = getClass();
172 		for (Method method : type.getMethods()) {
173 			if (!method.isAnnotationPresent(JawkBeforeStart.class)) {
174 				continue;
175 			}
176 			if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
177 				throw new IllegalStateException(
178 						"@" + JawkBeforeStart.class.getSimpleName()
179 								+ " does not support static methods: " + method.toGenericString());
180 			}
181 			Class<?>[] parameterTypes = method.getParameterTypes();
182 			if (method.getReturnType() != Void.TYPE
183 					|| parameterTypes.length != 2
184 					|| parameterTypes[0] != AVM.class
185 					|| parameterTypes[1] != JRT.class) {
186 				throw new IllegalStateException(
187 						"@" + JawkBeforeStart.class.getSimpleName()
188 								+ " method must declare void method(AVM, JRT): " + method.toGenericString());
189 			}
190 			method.setAccessible(true);
191 			discovered.add(method);
192 		}
193 		return discovered;
194 	}
195 
196 	/** {@inheritDoc} */
197 	@Override
198 	public Map<String, ExtensionFunction> getExtensionFunctions() {
199 		return getAnnotatedFunctions();
200 	}
201 
202 	/** {@inheritDoc} */
203 	@Override
204 	public void beforeStart(AVM avm, JRT runtime) {
205 		for (Method method : getBeforeStartMethods()) {
206 			try {
207 				method.invoke(this, avm, runtime);
208 			} catch (IllegalAccessException ex) {
209 				throw new IllegalStateException(
210 						"Unable to access extension before-start method " + method.toGenericString(),
211 						ex);
212 			} catch (InvocationTargetException ex) {
213 				Throwable cause = ex.getCause();
214 				if (cause instanceof RuntimeException) {
215 					throw (RuntimeException) cause;
216 				}
217 				if (cause instanceof Error) {
218 					throw (Error) cause;
219 				}
220 				throw new IllegalStateException(
221 						"Extension before-start method failed: " + method.toGenericString(),
222 						cause);
223 			}
224 		}
225 	}
226 }