Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.shaman.rpg.editor.objects.ui.properties; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Tree; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyEditor; import java.io.*; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.net.URI; import java.net.URISyntaxException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.commons.io.FileUtils; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.Task; import org.netbeans.api.project.Project; import org.openide.*; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; import org.openide.windows.WindowManager; import org.shaman.rpg.editor.objects.EditorLevelElement; import org.shaman.rpg.editor.objects.LevelElementValidator; import org.shaman.rpg.editor.objects.ObjectActionProvider; import org.shaman.rpg.editor.objects.PropertyEditorProvider; import org.shaman.rpg.editor.utilities.ClasspathAccess; import org.shaman.rpg.engine.core.obj.BaseController; import org.shaman.rpg.engine.core.obj.BaseModel; import org.shaman.rpg.engine.core.obj.BaseView; import org.shaman.rpg.engine.core.obj.controller.AdvancedLogicController; import org.shaman.rpg.engine.core.obj.controller.LogicScript; import org.shaman.rpg.engine.core.obj.model.ViewDelegateModel; /** * Support for {@link AdvancedLogicController} in the editor. * It provides a validator (that the script class implements {@link LogicScript}), * a property editor (that let you choose the class or create a new script class) * and a action provider (that creates initialization code for the script) * @author Sebastian Weiss */ @ServiceProviders({ @ServiceProvider(service = PropertyEditorProvider.class, position = 9000), @ServiceProvider(service = ObjectActionProvider.Factory.class, position = 9000), @ServiceProvider(service = LevelElementValidator.Factory.class, position = 9000) }) public class AdvancedLogicSupport implements PropertyEditorProvider, ObjectActionProvider.Factory, LevelElementValidator.Factory { private static final Logger LOG = Logger.getLogger(AdvancedLogicSupport.class.getName()); private static final String TEMPLATE_FILE = "Editor-Objects/TemplateLogicScript.java"; private Validator validator = new Validator(); private FileObject findFileObject(Project project, String className) { ClasspathAccess classpathAccess = project.getLookup().lookup(ClasspathAccess.class); if (classpathAccess == null) { LOG.info( "failed to access classpath: no implementation of ClasspathAccess found in the project's lookup"); return null; } FileObject fo = classpathAccess.getClassFile(className, ClasspathAccess.Language.JAVA, false); if (fo == null) { fo = classpathAccess.getClassFile(className, ClasspathAccess.Language.GROOVY, false); } if (fo == null) { return null; } return fo; } private boolean implementsLogicScript(JavaSource source) { final AtomicBoolean state = new AtomicBoolean(false); try { source.runWhenScanFinished(new Task<CompilationController>() { @Override public void run(CompilationController parameter) throws Exception { parameter.toPhase(JavaSource.Phase.PARSED); CompilationUnitTree tree = parameter.getCompilationUnit(); // System.out.println("compilation tree: "+tree); for (Tree typeDecl : tree.getTypeDecls()) { if (Tree.Kind.CLASS == typeDecl.getKind()) { ClassTree clazz = (ClassTree) typeDecl; // System.out.println("class tree: "+clazz); for (Tree implTree : clazz.getImplementsClause()) { if (implTree.toString().equals(LogicScript.class.getSimpleName()) || implTree.toString().equals(LogicScript.class.getCanonicalName())) { state.set(true); return; //class implements LogicScript } } } } state.set(false); } }, true).get(); } catch (IOException | InterruptedException | ExecutionException ex) { Exceptions.printStackTrace(ex); return false; } return state.get(); } private void createScriptFile(Project project, String packageClassName) { LOG.log(Level.INFO, "create new script file in project {0} under the class name {1}", new Object[] { project, packageClassName }); ClasspathAccess ac = project.getLookup().lookup(ClasspathAccess.class); if (ac.getClassFile(packageClassName, ClasspathAccess.Language.JAVA, false) != null) { LOG.info("script file already exists"); JOptionPane.showMessageDialog(null, "unable to create script file " + packageClassName + ", file already exists", "Unable to create script file", JOptionPane.ERROR_MESSAGE); return; } try { //create script file FileObject templateFile = FileUtil.getConfigFile(TEMPLATE_FILE); String template = templateFile.asText(); String className = packageClassName.substring(packageClassName.lastIndexOf('.') + 1); String packageName = packageClassName.substring(0, packageClassName.lastIndexOf('.')); template = template.replace("${packageName}", packageName); template = template.replace("${className}", className); FileObject target = ac.getClassFile(packageClassName, ClasspathAccess.Language.JAVA, true); try (OutputStream out = target.getOutputStream()) { out.write(template.getBytes()); } LOG.log(Level.INFO, "script file {0} created", packageClassName); } catch (IOException ex) { LOG.log(Level.SEVERE, "unable to create script file " + packageClassName, ex); JOptionPane.showMessageDialog(null, "Unable to create script file " + packageClassName, "Unable to create script file", JOptionPane.ERROR_MESSAGE); } } private void updateScriptFile(Project project, String className, AdvancedLogicController controller) { //TODO } @Override public PropertyEditor createPropertyEditor(PropertyDescriptor propertyDescriptor, ContextDescriptor contextDescriptor) { if (contextDescriptor.object instanceof AdvancedLogicController && propertyDescriptor.getter.getName().equals("getScript")) { //An AdvancedLogicController and the script property ClasspathAccess classpathAccess = contextDescriptor.assetManager.getProject().getLookup() .lookup(ClasspathAccess.class); if (classpathAccess == null) { LOG.info( "failed to access classpath: no implementation of ClasspathAccess found in the project's lookup"); return null; } return new PropEditor(contextDescriptor.assetManager.getProject(), classpathAccess); } return null; } @Override public <V extends BaseView, M extends BaseModel<? extends V>, C extends BaseController<? extends M>> ObjectActionProvider createActionProvider( EditorLevelElement<V, M, C> element, Object object, EditorLevelElement.ObjectType objectType) { if (object instanceof AdvancedLogicController) { return new ActionProvider(element, (AdvancedLogicController) object); } else { return null; } } @Override @SuppressWarnings("unchecked") public <V extends BaseView, M extends BaseModel<? extends V>, C extends BaseController<? extends M>> LevelElementValidator<V, M, C> createValidator( EditorLevelElement<V, M, C> element) { return (LevelElementValidator<V, M, C>) validator; } private class Validator implements LevelElementValidator<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> { @Override public String validate( EditorLevelElement<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> element) { for (BaseController<?> c : element.getControllers()) { if (c instanceof AdvancedLogicController) { String s = validate((AdvancedLogicController) c, element); if (s != null) { return s; } } } return null; } private String validate(AdvancedLogicController c, EditorLevelElement<?, ?, ?> element) { String script = c.getScript(); if (script == null) { return c.toString() + ": script is null"; } if (script.isEmpty()) { return c.toString() + ": script name is empty"; } FileObject fo = findFileObject(element.getLevel().getProject(), script); if (fo == null) { return c.toString() + ": script class not found"; } JavaSource source = JavaSource.forFileObject(fo); if (source != null) { if (!implementsLogicScript(source)) { return c.toString() + ": class does not implement LogicScript"; } } return null; } } private class PropEditor implements PropertyEditor { private Project project; private ClasspathAccess classpathAccess; private String string; private final PropertyChangeSupport support; private PropEditor(Project project, ClasspathAccess classpathAccess) { this.project = project; this.classpathAccess = classpathAccess; support = new PropertyChangeSupport(this); } @Override public void setValue(Object value) { String old = string; if (value instanceof String) { string = (String) value; } else { string = null; } support.firePropertyChange("value", old, string); // NOI18N } @Override public String getValue() { return string; } @Override public boolean isPaintable() { return false; } @Override public void paintValue(Graphics gfx, Rectangle box) { throw new UnsupportedOperationException("Not supported."); } @Override public String getAsText() { if (string == null) { return "<null value>"; } else { return string; } } @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null) { throw new IllegalArgumentException("null parameter"); // NOI18N } text = text.trim(); if ("<null value>".equals(text)) { // NOI18N setValue(null); return; } setValue(text); } @Override public String getJavaInitializationString() { if (string == null) { return "null"; // NOI18N } else { return "\"" + string + "\""; // NOI18N } } @Override public String[] getTags() { return null; } @Override public Component getCustomEditor() { return new PropEditorDialog(this); } @Override public boolean supportsCustomEditor() { return true; } @Override public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } } private class PropEditorDialog extends JDialog { private final PropEditor editor; /** * A return status code - returned if Cancel button has been pressed */ public static final int RET_CANCEL = 0; /** * A return status code - returned if OK button has been pressed */ public static final int RET_OK = 1; private PropEditorDialog(final PropEditor editor) { super(WindowManager.getDefault().getMainWindow(), true); this.editor = editor; initComponents(); setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); SwingWorker<Void, Void> w = new SwingWorker<Void, Void>() { private DefaultListModel<String> classes; @Override protected Void doInBackground() throws Exception { ClasspathAccess ac = editor.classpathAccess; ArrayList<String> c = new ArrayList<>(); for (Enumeration<String> e = ac.listClasses(true); e.hasMoreElements();) { String className = e.nextElement(); FileObject fo = ac.getClassFile(className, ClasspathAccess.Language.JAVA, false); if (fo != null) { JavaSource source = JavaSource.forFileObject(fo); if (source != null && implementsLogicScript(source)) { c.add(className); } } } Collections.sort(c); classes = new DefaultListModel<>(); for (String s : c) { classes.addElement(s); } return null; } @Override protected void done() { super.done(); classList.setModel(classes); if (!classes.isEmpty()) { okButton.setEnabled(true); } } }; w.execute(); } /** * @return the return status of this dialog - one of RET_OK or * RET_CANCEL */ public int getReturnStatus() { return returnStatus; } @SuppressWarnings("unchecked") private void initComponents() { okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); classList = new JList<>(new String[] { org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.wait") }); classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane scrollPane = new JScrollPane(classList); scrollPane.setMinimumSize(new Dimension(300, 400)); okButton.setEnabled(false); createNewButton = new JButton(org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.createNew")); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setTitle(org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.title")); // NOI18N setResizable(true); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle .getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.okButton.text")); // NOI18N okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okButtonActionPerformed(evt); } }); org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle .getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.cancelButton.text")); // NOI18N cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); classList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { listSelectionChangeEvent(e); } }); createNewButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { createNewScriptEvent(); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup().addComponent(createNewButton) .addGap(0, 0, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) .addGroup(layout.createSequentialGroup().addComponent(scrollPane))).addContainerGap())); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] { cancelButton, okButton }); layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup( javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(scrollPane)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(createNewButton).addComponent(cancelButton) .addComponent(okButton)) .addContainerGap())); getRootPane().setDefaultButton(okButton); pack(); } private void listSelectionChangeEvent(ListSelectionEvent e) { okButton.setEnabled(true); } private void createNewScriptEvent() { String s = JOptionPane.showInputDialog(this, NbBundle.getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.scriptNameDialog")); if (s == null || s.isEmpty()) { return; } //create new script createScriptFile(editor.project, s); editor.setValue(s); doClose(RET_OK); } private void okButtonActionPerformed(java.awt.event.ActionEvent evt) { String className = classList.getSelectedValue(); editor.setValue(className); doClose(RET_OK); } private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) { doClose(RET_CANCEL); } /** * Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) { doClose(RET_CANCEL); } private void doClose(int retStatus) { returnStatus = retStatus; setVisible(false); dispose(); } private javax.swing.JButton cancelButton; private javax.swing.JButton okButton; private JList<String> classList; //TODO: may change this to a tree private JButton createNewButton; private int returnStatus = RET_CANCEL; } public class ActionProvider implements ObjectActionProvider { private final EditorLevelElement<?, ?, ?> element; private final AdvancedLogicController controller; private final ClasspathAccess classpathAccess; private String oldClassName; private String[] oldModelNames; private Class<? extends BaseModel<?>>[] oldModelClasses; public ActionProvider(EditorLevelElement<?, ?, ?> element, AdvancedLogicController controller) { this.element = element; this.controller = controller; classpathAccess = element.getLevel().getProject().getLookup().lookup(ClasspathAccess.class); //read old properties oldClassName = controller.getScript(); BaseModel[] models = controller.getModels().toArray(new BaseModel[0]); oldModelClasses = new Class[models.length]; oldModelNames = new String[models.length]; for (int i = 0; i < models.length; ++i) { if (models[i] == null) { oldModelClasses[i] = null; oldModelNames[i] = null; } else { oldModelClasses[i] = (Class<? extends BaseModel<?>>) models[i].getClass(); oldModelNames[i] = controller.getSlotNames()[i]; } } if (classpathAccess.getClassFile(oldClassName, ClasspathAccess.Language.JAVA, false) == null) { oldClassName = null; //class does not exists } } @Override public Action[] getContextActions() { return new Action[0]; //no actions } @Override public Action getPreferedAction() { return null; } //TODO: lokalize error dialogs @Override public void onApprove() { //read new properties String newClassName = controller.getScript(); BaseModel[] models = controller.getModels().toArray(new BaseModel[0]); Class[] newModelClasses = new Class[models.length]; String[] newModelNames = new String[models.length]; for (int i = 0; i < models.length; ++i) { if (models[i] == null) { newModelClasses[i] = null; newModelNames[i] = null; } else { newModelClasses[i] = (Class<? extends BaseModel<?>>) models[i].getClass(); newModelNames[i] = controller.getSlotNames()[i]; } } FileObject scriptClass = classpathAccess.getClassFile(newClassName, ClasspathAccess.Language.JAVA, false); if (scriptClass == null) { LOG.info("script does not exists, nothing to change"); return; //script does not exists } //check if the script file must be modified if (Objects.equals(newClassName, oldClassName) && Arrays.equals(newModelClasses, oldModelClasses) && Arrays.equals(newModelNames, oldModelNames)) { LOG.info("no changes in the controller, no need to change the controller"); return; //no changes } //there are changes, change the script //read original file and find the code blocks to change String[] lines; try { lines = scriptClass.asLines().toArray(new String[0]); } catch (IOException ex) { LOG.log(Level.SEVERE, "unable to read script file", ex); JOptionPane.showMessageDialog(null, "Unable to open script file,\n script file will not be modified.\n" + ex.getLocalizedMessage(), "Unable to open script file", JOptionPane.ERROR_MESSAGE); return; } String[] trimmedLines = new String[lines.length]; for (int i = 0; i < lines.length; ++i) { trimmedLines[i] = lines[i].trim(); } int startVars = -1; int endVars = -1; int startInit = -1; int endInit = -1; for (int i = 0; i < lines.length; ++i) { if (trimmedLines[i].startsWith("//GEN_START_VARS")) { startVars = i; for (int j = i + 1; j < lines.length; ++j) { if (trimmedLines[j].startsWith("//GEN_END_VARS")) { endVars = j; break; } } break; } } if (startVars == -1) { LOG.info("no GEN_START_VARS found"); JOptionPane.showMessageDialog(null, "No GEN_START_VARS found, code generator cannot be executed", "Invalid script file", JOptionPane.ERROR_MESSAGE); return; } if (endVars == -1) { LOG.info("no GEN_END_VARS found"); JOptionPane.showMessageDialog(null, "No GEN_END_VARS found, code generator cannot be executed", "Invalid script file", JOptionPane.ERROR_MESSAGE); return; } for (int i = 0; i < lines.length; ++i) { if (trimmedLines[i].startsWith("//GEN_START_INIT")) { startInit = i; for (int j = i + 1; j < lines.length; ++j) { if (trimmedLines[j].startsWith("//GEN_END_INIT")) { endInit = j; break; } } break; } } if (startInit == -1) { LOG.info("no GEN_START_INIT found"); JOptionPane.showMessageDialog(null, "No GEN_START_INIT found, code generator cannot be executed", "Invalid script file", JOptionPane.ERROR_MESSAGE); return; } if (endInit == -1) { LOG.info("no GEN_END_INIT found"); JOptionPane.showMessageDialog(null, "No GEN_END_INIT found, code generator cannot be executed", "Invalid script file", JOptionPane.ERROR_MESSAGE); return; } //generate code to insert String[] variableDeclarations = new String[newModelClasses.length]; String[] variableAssignments = new String[newModelClasses.length]; for (int i = 0; i < newModelClasses.length; ++i) { variableDeclarations[i] = getVariableDeclaration(models[i], newModelNames[i]); variableAssignments[i] = getVariableAssignement(models[i], newModelNames[i]); } //write file try (PrintWriter writer = new PrintWriter(scriptClass.getOutputStream())) { boolean declarationFirst = startVars < startInit; //write start of the file for (int i = 0; i < Math.min(startVars, startInit); ++i) { writer.println(lines[i]); } if (declarationFirst) { //write variable declaration String prefix = lines[startVars].substring(0, lines[startVars].indexOf('/')); writer.println(lines[startVars]); for (int i = 0; i < variableDeclarations.length; ++i) { writer.print(prefix); writer.println(variableDeclarations[i]); } writer.println(lines[endVars]); //write until assignement for (int i = endVars; i < startInit; ++i) { writer.println(lines[i]); } //write assignement prefix = lines[startInit].substring(0, lines[startInit].indexOf('/')); writer.println(lines[startInit]); for (int i = 0; i < variableAssignments.length; ++i) { writer.print(prefix); writer.println(variableAssignments[i]); } writer.println(lines[endInit]); } else { //write assignement String prefix = lines[startInit].substring(0, lines[startInit].indexOf('/')); writer.println(lines[startInit]); for (int i = 0; i < variableAssignments.length; ++i) { writer.print(prefix); writer.println(variableAssignments[i]); } writer.println(lines[endInit]); //write until assignement for (int i = endVars; i < startInit; ++i) { writer.println(lines[i]); } //write variable declaration prefix = lines[startVars].substring(0, lines[startVars].indexOf('/')); writer.println(lines[startVars]); for (int i = 0; i < variableDeclarations.length; ++i) { writer.print(prefix); writer.println(variableDeclarations[i]); } writer.println(lines[endVars]); } //write until end of file for (int i = Math.max(endInit, endVars) + 1; i < lines.length; ++i) { writer.println(lines[i]); } } catch (IOException ex) { LOG.log(Level.SEVERE, "unable to write script file", ex); JOptionPane.showMessageDialog(null, ex.getLocalizedMessage(), "Unable to write script file", JOptionPane.ERROR_MESSAGE); } LOG.info("script file changed"); } @Override public void onCancel() { //do nothing } } private static String toJavaIdentifier(String name) { //TODO: check if the name is a valid java variable name //If not, correct it return name; } private static String getVariableType(BaseModel model) { StringBuilder s = new StringBuilder(); Class<? extends BaseModel> mc = model.getClass(); s.append(mc.getCanonicalName()); if (mc.equals(ViewDelegateModel.class)) { //special case: view delegate model, use class of the view BaseView v = ((ViewDelegateModel) model).getView(); if (v != null) { Class<? extends BaseView> vc = v.getClass(); s.append("<? extends "); s.append(vc.getCanonicalName()); s.append(">"); return s.toString(); } } TypeVariable[] typeVars = mc.getTypeParameters(); if (typeVars.length > 0) { s.append("<"); for (int i = 0; i < typeVars.length; ++i) { if (i > 0) s.append(", "); resolveType(s, typeVars[i]); } s.append(">"); } return s.toString(); } public static String getVariableDeclaration(BaseModel model, String variable) { StringBuilder s = new StringBuilder(); s.append("private "); s.append(getVariableType(model)); s.append(" "); s.append(toJavaIdentifier(variable)); s.append(";"); return s.toString(); } public static String getVariableAssignement(BaseModel model, String variable) { StringBuilder s = new StringBuilder(); s.append(toJavaIdentifier(variable)); s.append(" = ("); s.append(getVariableType(model)); s.append(") models.get(\""); s.append(variable); s.append("\");"); return s.toString(); } private static void resolveType(StringBuilder s, Type t) { if (t instanceof TypeVariable) { Type[] bounds = ((TypeVariable) t).getBounds(); s.append("? extends "); for (int j = 0; j < bounds.length; ++j) { if (j > 0) s.append(", "); resolveType(s, bounds[j]); } } else if (t instanceof Class) { s.append(((Class) t).getCanonicalName()); } else if (t instanceof ParameterizedType) { resolveType(s, ((ParameterizedType) t).getRawType()); Type[] args = ((ParameterizedType) t).getActualTypeArguments(); if (args.length > 0) { s.append("<"); for (int j = 0; j < args.length; ++j) { if (j > 0) s.append(", "); resolveType(s, args[j]); } s.append(">"); } } else if (t instanceof WildcardType) { Type[] bounds = ((WildcardType) t).getUpperBounds(); if (bounds.length > 0) { if (bounds.length == 1 && bounds[0].equals(Object.class)) { return; //no upper bound, this should not happen } s.append("? extends "); for (int j = 0; j < bounds.length; ++j) { if (j > 0) s.append(", "); resolveType(s, bounds[j]); } } } else { LOG.info("unknown type: " + t + " of class " + t.getClass()); } } }