/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 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.j2db.documentation; import java.awt.Color; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.print.PrinterJob; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.json.JSONArray; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeJavaArray; import org.mozilla.javascript.Scriptable; import com.servoy.base.scripting.api.IJSController; import com.servoy.base.scripting.api.IJSDataSet; import com.servoy.base.scripting.api.IJSFoundSet; import com.servoy.base.scripting.api.IJSRecord; import com.servoy.base.solutionmodel.IBaseSMButton; import com.servoy.base.solutionmodel.IBaseSMComponent; import com.servoy.base.solutionmodel.IBaseSMField; import com.servoy.base.solutionmodel.IBaseSMForm; import com.servoy.base.solutionmodel.IBaseSMLabel; import com.servoy.base.solutionmodel.IBaseSMMethod; import com.servoy.base.solutionmodel.IBaseSMPart; import com.servoy.base.solutionmodel.IBaseSMValueList; import com.servoy.base.solutionmodel.IBaseSMVariable; import com.servoy.base.solutionmodel.mobile.IMobileSMForm; import com.servoy.base.solutionmodel.mobile.IMobileSMLabel; import com.servoy.j2db.IForm; import com.servoy.j2db.dataprocessing.FoundSet; import com.servoy.j2db.dataprocessing.IDataSet; import com.servoy.j2db.dataprocessing.IFoundSet; import com.servoy.j2db.dataprocessing.IFoundSetInternal; import com.servoy.j2db.dataprocessing.IRecord; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.dataprocessing.JSDataSet; import com.servoy.j2db.dataprocessing.Record; import com.servoy.j2db.dataprocessing.RelatedFoundSet; import com.servoy.j2db.querybuilder.IQueryBuilderCondition; import com.servoy.j2db.querybuilder.IQueryBuilderLogicalCondition; import com.servoy.j2db.querybuilder.IQueryBuilderWhereCondition; import com.servoy.j2db.querybuilder.impl.QBCondition; import com.servoy.j2db.querybuilder.impl.QBLogicalCondition; import com.servoy.j2db.querybuilder.impl.QBWhereCondition; import com.servoy.j2db.scripting.FormScope; import com.servoy.j2db.scripting.IScriptable; import com.servoy.j2db.scripting.JSMap; import com.servoy.j2db.scripting.solutionmodel.JSButton; import com.servoy.j2db.scripting.solutionmodel.JSComponent; import com.servoy.j2db.scripting.solutionmodel.JSField; import com.servoy.j2db.scripting.solutionmodel.JSFieldWithConstants; import com.servoy.j2db.scripting.solutionmodel.JSLabel; import com.servoy.j2db.scripting.solutionmodel.JSMethod; import com.servoy.j2db.scripting.solutionmodel.JSPart; import com.servoy.j2db.scripting.solutionmodel.JSVariable; import com.servoy.j2db.solutionmodel.ISMButton; import com.servoy.j2db.solutionmodel.ISMComponent; import com.servoy.j2db.solutionmodel.ISMField; import com.servoy.j2db.solutionmodel.ISMForm; import com.servoy.j2db.solutionmodel.ISMLabel; import com.servoy.j2db.solutionmodel.ISMMethod; import com.servoy.j2db.solutionmodel.ISMPart; import com.servoy.j2db.solutionmodel.ISMVariable; import com.servoy.j2db.ui.IComponent; import com.servoy.j2db.ui.IScriptRenderMethods; import com.servoy.j2db.ui.IScriptRenderMethodsWithOptionalProps; import com.servoy.j2db.ui.runtime.IRuntimeComponent; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ServoyException; /** * A translator class capable of translating Java classes (used in javascript as return/parameter types) either to another java class that is * ServoyDocumented or scriptable (in case the given class isn't already), or directly to a javascript type name (scripting name for ServoyDocumented annotation). * @author acostescu */ public class JavaToDocumentedJSTypeTranslator { private final Map<Class< ? >, Class< ? >> javaClassToDocumentedJavaClass = new HashMap<Class< ? >, Class< ? >>(); private final Map<String, String> javaClassToDocumentedJavaClassWorkarounds = new HashMap<String, String>(); private final Map<Class< ? >, String> cachedDocumentedJavaClassNames = new ConcurrentHashMap<Class< ? >, String>(); private final Map<Class< ? >, String> cachedJSTypeNames = new ConcurrentHashMap<Class< ? >, String>(); JavaToDocumentedJSTypeTranslator() { initializeClassTranslationMap(); } /** * see {@link #translateJavaClassToJSDocumented(Class)} */ private void initializeClassTranslationMap() { // -------------------------------- IMPORTANT ------------------------------------- // if you CHANGE something in this mappings you might need to update TypeMapper's map as well javaClassToDocumentedJavaClass.put(Boolean.class, com.servoy.j2db.documentation.scripting.docs.Boolean.class); javaClassToDocumentedJavaClass.put(Boolean.TYPE, com.servoy.j2db.documentation.scripting.docs.Boolean.class); javaClassToDocumentedJavaClass.put(Double.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Double.TYPE, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Float.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Float.TYPE, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Long.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Long.TYPE, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Integer.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Integer.TYPE, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Byte.class, Byte.TYPE); // javaClassToDocumentedJavaClass.put(Byte.TYPE, Byte.TYPE); javaClassToDocumentedJavaClass.put(Short.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Short.TYPE, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(Number.class, com.servoy.j2db.documentation.scripting.docs.Number.class); javaClassToDocumentedJavaClass.put(java.util.Date.class, com.servoy.j2db.documentation.scripting.docs.Date.class); javaClassToDocumentedJavaClass.put(java.sql.Date.class, com.servoy.j2db.documentation.scripting.docs.Date.class); javaClassToDocumentedJavaClass.put(java.lang.Character.class, com.servoy.j2db.documentation.scripting.docs.String.class); javaClassToDocumentedJavaClass.put(Character.TYPE, com.servoy.j2db.documentation.scripting.docs.String.class); javaClassToDocumentedJavaClass.put(String.class, com.servoy.j2db.documentation.scripting.docs.String.class); javaClassToDocumentedJavaClass.put(Dimension.class, com.servoy.j2db.documentation.scripting.docs.String.class); // why not Object? javaClassToDocumentedJavaClass.put(Insets.class, com.servoy.j2db.documentation.scripting.docs.String.class); // why not Object? javaClassToDocumentedJavaClass.put(Point.class, com.servoy.j2db.documentation.scripting.docs.String.class); // why not Object? javaClassToDocumentedJavaClass.put(Color.class, com.servoy.j2db.documentation.scripting.docs.String.class); // why not Object? javaClassToDocumentedJavaClass.put(NativeArray.class, com.servoy.j2db.documentation.scripting.docs.Array.class); javaClassToDocumentedJavaClass.put(NativeJavaArray.class, com.servoy.j2db.documentation.scripting.docs.Array.class); javaClassToDocumentedJavaClass.put(JSONArray.class, com.servoy.j2db.documentation.scripting.docs.Array.class); javaClassToDocumentedJavaClass.put(Object.class, com.servoy.j2db.documentation.scripting.docs.Object.class); javaClassToDocumentedJavaClass.put(Scriptable.class, com.servoy.j2db.documentation.scripting.docs.Object.class); javaClassToDocumentedJavaClass.put(JSMap.class, com.servoy.j2db.documentation.scripting.docs.Object.class); javaClassToDocumentedJavaClass.put(Map.class, com.servoy.j2db.documentation.scripting.docs.Object.class); javaClassToDocumentedJavaClass.put(org.mozilla.javascript.Function.class, com.servoy.j2db.documentation.scripting.docs.Function.class); javaClassToDocumentedJavaClass.put(Exception.class, ServoyException.class); javaClassToDocumentedJavaClass.put(IFoundSetInternal.class, FoundSet.class); javaClassToDocumentedJavaClass.put(IJSFoundSet.class, FoundSet.class); javaClassToDocumentedJavaClass.put(IFoundSet.class, FoundSet.class); javaClassToDocumentedJavaClass.put(RelatedFoundSet.class, FoundSet.class); javaClassToDocumentedJavaClass.put(IDataSet.class, JSDataSet.class); javaClassToDocumentedJavaClass.put(IJSDataSet.class, JSDataSet.class); javaClassToDocumentedJavaClass.put(IRecordInternal.class, Record.class); javaClassToDocumentedJavaClass.put(IJSRecord.class, Record.class); javaClassToDocumentedJavaClass.put(IRecord.class, Record.class); javaClassToDocumentedJavaClass.put(IComponent.class, IRuntimeComponent.class); javaClassToDocumentedJavaClass.put(ISMField.class, JSFieldWithConstants.class); javaClassToDocumentedJavaClass.put(IBaseSMField.class, JSFieldWithConstants.class); javaClassToDocumentedJavaClass.put(JSField.class, JSFieldWithConstants.class); // javaClassToDocumentedJavaClass.put(ISMComponent.class, JSComponent.class); javaClassToDocumentedJavaClass.put(IBaseSMComponent.class, JSComponent.class); javaClassToDocumentedJavaClass.put(ISMComponent.class, JSComponent.class); javaClassToDocumentedJavaClass.put(ISMMethod.class, JSMethod.class); javaClassToDocumentedJavaClass.put(ISMVariable.class, JSVariable.class); javaClassToDocumentedJavaClass.put(IBaseSMMethod.class, JSMethod.class); javaClassToDocumentedJavaClass.put(IBaseSMVariable.class, JSVariable.class); javaClassToDocumentedJavaClass.put(ISMButton.class, JSButton.class); javaClassToDocumentedJavaClass.put(ISMLabel.class, JSLabel.class); javaClassToDocumentedJavaClass.put(IBaseSMButton.class, JSButton.class); javaClassToDocumentedJavaClass.put(IBaseSMLabel.class, JSLabel.class); javaClassToDocumentedJavaClass.put(IMobileSMLabel.class, JSLabel.class); javaClassToDocumentedJavaClass.put(ISMPart.class, com.servoy.j2db.scripting.solutionmodel.JSPartWithConstants.class); javaClassToDocumentedJavaClass.put(IBaseSMPart.class, com.servoy.j2db.scripting.solutionmodel.JSPartWithConstants.class); javaClassToDocumentedJavaClass.put(JSPart.class, com.servoy.j2db.scripting.solutionmodel.JSPartWithConstants.class); javaClassToDocumentedJavaClass.put(ISMForm.class, com.servoy.j2db.scripting.solutionmodel.JSForm.class); javaClassToDocumentedJavaClass.put(IBaseSMForm.class, com.servoy.j2db.scripting.solutionmodel.JSForm.class); javaClassToDocumentedJavaClass.put(IMobileSMForm.class, com.servoy.j2db.scripting.solutionmodel.JSForm.class); javaClassToDocumentedJavaClass.put(IBaseSMValueList.class, com.servoy.j2db.scripting.solutionmodel.JSValueList.class); javaClassToDocumentedJavaClass.put(IForm.class, com.servoy.j2db.documentation.scripting.docs.Form.class); javaClassToDocumentedJavaClass.put(FormScope.class, com.servoy.j2db.documentation.scripting.docs.Form.class); javaClassToDocumentedJavaClass.put(IJSController.class, com.servoy.j2db.documentation.scripting.docs.Form.class); javaClassToDocumentedJavaClass.put(IQueryBuilderCondition.class, QBCondition.class); javaClassToDocumentedJavaClass.put(IQueryBuilderLogicalCondition.class, QBLogicalCondition.class); javaClassToDocumentedJavaClass.put(IQueryBuilderWhereCondition.class, QBWhereCondition.class); javaClassToDocumentedJavaClass.put(IScriptRenderMethods.class, IScriptRenderMethodsWithOptionalProps.class); // this might look strange, it's here to avoid PrinterJob types to be changed to Object, because they are not Scriptable or ServoyDocumented // but we still want them to appear in JS with their original name javaClassToDocumentedJavaClass.put(PrinterJob.class, com.servoy.j2db.documentation.scripting.docs.PrinterJob.class); // (plugin) workarounds - ideally this map should tend to become empty in the future instead of growing javaClassToDocumentedJavaClassWorkarounds.put( "com.servoy.extensions.plugins.window.menu.AbstractMenuItem", "com.servoy.extensions.plugins.window.menu.MenuItem"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Translate some java class that is used in JS code (because it is returned somewhere) into a JS (@ServoyDocumented) documented class. * The method is able to handle arrays. * @param javaClass the initial java class. * @return the class name (Class.getName()) of the translation class. (that should make sense in java-script) If the given class is null it returns null. */ public String translateJavaClassToJSDocumentedJavaClassName(Class< ? > javaClass) { if (javaClass == null) return null; String cached = cachedDocumentedJavaClassNames.get(javaClass); if (cached != null) return cached; String translatedClassName; Pair<Class< ? >, Integer> classAndArray = splitArrayIfNeeded(javaClass); if (classAndArray.getLeft().equals(Byte.TYPE)) { translatedClassName = Byte.TYPE.getName(); } else { Pair<Class< ? >, String> translatedClassAndName = translateJavaClassToJSDocumented(classAndArray.getLeft()); translatedClassName = translatedClassAndName.getRight(); } cached = mergeBackArrayTypeIfNeeded(translatedClassName, classAndArray.getRight().intValue()); cachedDocumentedJavaClassNames.put(javaClass, cached); return cached; } /** * Translate a java class that can be used in JS code into a JS Type name. It first tries to translate the class into a java-script documented * class (@ServoyDocumented) if needed, then it tries to get the scripting name defined in the annotation. If this fails, it will just return the * simple class name of the original or translated class. <BR> * The method is able to handle arrays. * @param javaClass the class to be translated. * @return a java-script type that makes sense for the given java class. If the given class is null it returns null. */ public String translateJavaClassToJSTypeName(Class< ? > javaClass) { if (javaClass == null) return null; String cached = cachedJSTypeNames.get(javaClass); if (cached != null) return cached; Pair<Class< ? >, Integer> classAndArray = splitArrayIfNeeded(javaClass); String jsType; if (classAndArray.getLeft().equals(Byte.TYPE)) { jsType = Byte.TYPE.getName(); } else { Pair<Class< ? >, String> translatedClassAndName = translateJavaClassToJSDocumented(classAndArray.getLeft()); if (translatedClassAndName.getLeft() == null) { // we got to this translation because of a String workaround; just parse the class name and get the class' simple name int dotIdx = translatedClassAndName.getRight().lastIndexOf('.'); if (dotIdx >= 0) jsType = translatedClassAndName.getRight().substring(dotIdx + 1); else jsType = translatedClassAndName.getRight(); } else { // this is the usual case; // we have a class; use it's scripting name when it's a @ServoyDocumented class or it's simple class name otherwise if (translatedClassAndName.getLeft().isAnnotationPresent(ServoyDocumented.class)) { ServoyDocumented annotation = translatedClassAndName.getLeft().getAnnotation(ServoyDocumented.class); jsType = annotation.scriptingName(); if (jsType == null || jsType.trim().length() == 0) jsType = annotation.publicName(); if (jsType == null || jsType.trim().length() == 0) jsType = translatedClassAndName.getLeft().getSimpleName(); if (jsType != null) jsType = jsType.trim(); } else if (translatedClassAndName.getRight() != null && translatedClassAndName.getRight().startsWith("Packages.")) { jsType = translatedClassAndName.getRight(); } else { jsType = translatedClassAndName.getLeft().getSimpleName(); } } } cached = mergeBackArrayTypeIfNeeded(jsType, classAndArray.getRight().intValue()); cachedJSTypeNames.put(javaClass, cached); return cached; } /** * Tries to translate a non-array java type into a JS documented java type. * The method prints warnings in the log for unexpected classes that are exposed to JS. * @param javaClass the class to translate. * @return a Class if possible, if not possible then null class (most of the time is not-null) and an always non-null class name (Class.getName()). */ private Pair<Class< ? >, String> translateJavaClassToJSDocumented(Class< ? > javaClass) { Class< ? > cls = javaClass; String translatedClassName; Class< ? > tmpClass = javaClassToDocumentedJavaClass.get(cls); if (tmpClass == null) { String tmpClassName = javaClassToDocumentedJavaClassWorkarounds.get(cls.getName()); if (tmpClassName == null) { if (cls.equals(Void.TYPE)) translatedClassName = Void.TYPE.toString(); else { if (cls.isAnnotationPresent(ServoyDocumented.class)) translatedClassName = cls.getName(); else if (IScriptable.class.isAssignableFrom(cls)) { // makes some sense to be used in JS, but is not documented Debug.trace("Undocumented scriptable type exposed to JS: " + cls.getName() + "."); //$NON-NLS-1$ //$NON-NLS-2$ translatedClassName = cls.getName(); } else { Debug.trace("Undocumented/non-scriptable type exposed to JS: " + cls.getName() + ". Changed into the rhino 'Packages.' notation"); //$NON-NLS-1$ //$NON-NLS-2$ // cls = com.servoy.j2db.documentation.scripting.docs.Object.class; // translatedClassName = com.servoy.j2db.documentation.scripting.docs.Object.class.getName(); translatedClassName = "Packages." + cls.getName(); } } } else { try { cls = null; // we only have a workaround string representation of the translation; try to get the class from that cls = getClass().getClassLoader().loadClass(tmpClassName); } catch (ClassNotFoundException e) { // oh well, we tried, it's probably a plugin class } translatedClassName = tmpClassName; } } else { cls = tmpClass; translatedClassName = tmpClass.getName(); } return new Pair<Class< ? >, String>(cls, translatedClassName); } /** * If the class is an array, split it up into array dimension and element type (class). * @return a pair containing the element type and the array's dimension. The array's dimension is 0 and element type is the given type if it's not an array. */ private Pair<Class< ? >, Integer> splitArrayIfNeeded(Class< ? > javaClass) { int arrDim = 0; Class< ? > c = javaClass; while (c.isArray()) { arrDim++; c = c.getComponentType(); } return new Pair<Class< ? >, Integer>(c, Integer.valueOf(arrDim)); } /** * If the class was an array that was split, merge it's "name" back based on element type and dimensions. * @return the string representation of */ private String mergeBackArrayTypeIfNeeded(String javaClassName, int dimensions) { String rez = javaClassName; for (int i = 0; i < dimensions; i++) rez += "[]"; //$NON-NLS-1$ return rez; } }