/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2013 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.base.nongwt.test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.servoy.base.nongwt.test.ILineMapper.LineMapping; /** * Class that holds mappings from mobile generated solution JS to lines in the developer scope/form js files. * Useful for better jsunit test stack traces. * * The line numbers in the map are 1 - based. * * @author acostescu */ @SuppressWarnings("nls") public class LineMapper implements ILineMapper { // currently we don't remember the script file path, as the test client can already locate that based on generated function name private final Map<Long, LineMapping> mobileLineToDeveloperLine = new HashMap<Long, LineMapping>(128); protected final static Pattern HYBUGGER_LINE_EXTRACT_REGEX = Pattern.compile("JsHybugger\\.track\\(.*,\\D*(\\d*),.*\\);"); private final static String PROP_PREFIX = "m"; /** * In this case the function's code is actually no longer the same as in developer; we need to map based on hybugger injected code. * @param startMobileJSLine the line where this code will start in the exported mobile JS file. * @param code hybugger injected code; parse and use the line numbers in there. * @param filePath workspace relative file path to JS file. */ public void mapFunctionDebugMode(long startMobileJSLine, String code, String filePath) { // hybugger lines are 0 - based (so editor line 18 is actually hybugger line 17) // example: // JsHybugger.track('http://127.0.0.1:8080/servoy_sample_mobile/forms/companies.js', 30, false); // application.output("b"); String[] lines = code.split("\r\n|\n|\r"); Matcher m; for (int i = 0; i < lines.length; i++) { m = HYBUGGER_LINE_EXTRACT_REGEX.matcher(lines[i].trim()); if (m.matches()) { i++; mobileLineToDeveloperLine.put(Long.valueOf(startMobileJSLine + i), new LineMapping(Long.valueOf(m.group(1)).longValue() + 1, filePath)); } } } /** * The function code is identical as in developer. We only need to map all lines to their mobile counterparts. * @param startMobileJSLine the line where this code will start in the exported mobile JS file. * @param endMobileJSLine the line where this code will end in the exported mobile JS file. * @param filePath workspace relative file path to JS file. * @param startDeveloperJSLine start 1 - based line in developer js editor. */ public void mapFunction(long startMobileJSLine, long endMobileJSLine, String filePath, int startDeveloperJSLine) { for (long l = startMobileJSLine; l < endMobileJSLine; l++) { mobileLineToDeveloperLine.put(Long.valueOf(l), new LineMapping(startDeveloperJSLine + l - startMobileJSLine, filePath)); } } public LineMapping mapToDeveloperScript(long mobileJSlineNumber) { return mobileLineToDeveloperLine.get(Long.valueOf(mobileJSlineNumber)); } /** * Writes the mappings as a property file: * m[mobile_line_number]=[dev_line_number],[dev_path] */ public InputStream toProperties() throws IOException { ByteArrayOutputStream out = null; ByteArrayInputStream in = null; try { out = new ByteArrayOutputStream(mobileLineToDeveloperLine.size() * 30); Properties prop = new Properties(); for (Map.Entry<Long, LineMapping> e : mobileLineToDeveloperLine.entrySet()) { prop.put(PROP_PREFIX + e.getKey(), e.getValue().lineNumber + "," + e.getValue().file); } prop.store(out, null); in = new ByteArrayInputStream(out.toByteArray()); } finally { if (out != null) out.close(); } return in; } /** * Writes the mappings as a property file: * m[mobile_line_number]=[dev_line_number],[dev_path] */ public static LineMapper fromProperties(InputStream propertiesStream) throws IOException { LineMapper mapper = new LineMapper(); try { Properties prop = new Properties(); prop.load(propertiesStream); for (Map.Entry<Object, Object> e : prop.entrySet()) { String value = e.getValue().toString(); mapper.mobileLineToDeveloperLine.put(Long.valueOf(e.getKey().toString().substring(PROP_PREFIX.length())), new LineMapping(Long.valueOf(value.substring(0, value.indexOf(','))).longValue(), value.substring(value.indexOf(',') + 1))); } } finally { if (propertiesStream != null) propertiesStream.close(); } return mapper; } }