Java tutorial
/******************************************************************************* * Copyright (c) 2011 Google, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Google, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.wb.internal.core.model; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.eclipse.wb.core.editor.IDesignPageSite; import org.eclipse.wb.core.eval.ExecutionFlowDescription; import org.eclipse.wb.core.eval.ExecutionFlowUtils; import org.eclipse.wb.core.eval.ExecutionFlowUtils.ExecutionFlowFrameVisitor; import org.eclipse.wb.core.eval.ExecutionFlowUtils.VisitingContext; import org.eclipse.wb.core.model.AbstractComponentInfo; import org.eclipse.wb.core.model.IWrapperInfo; import org.eclipse.wb.core.model.JavaInfo; import org.eclipse.wb.core.model.ObjectInfo; import org.eclipse.wb.core.model.ObjectInfoUtils; import org.eclipse.wb.core.model.association.Association; import org.eclipse.wb.core.model.association.AssociationObject; import org.eclipse.wb.core.model.association.CompoundAssociation; import org.eclipse.wb.core.model.association.ImplicitObjectAssociation; import org.eclipse.wb.core.model.association.InvocationChildArrayAssociation; import org.eclipse.wb.core.model.association.InvocationChildEllipsisAssociation; import org.eclipse.wb.core.model.association.UnknownAssociation; import org.eclipse.wb.core.model.broadcast.EvaluationEventListener; import org.eclipse.wb.internal.core.DesignerPlugin; import org.eclipse.wb.internal.core.model.creation.CreationSupport; import org.eclipse.wb.internal.core.model.creation.ExposedFieldCreationSupport; import org.eclipse.wb.internal.core.model.creation.ExposedPropertyCreationSupport; import org.eclipse.wb.internal.core.model.creation.IExposedCreationSupport; import org.eclipse.wb.internal.core.model.creation.IImplicitCreationSupport; import org.eclipse.wb.internal.core.model.creation.IWrapperControlCreationSupport; import org.eclipse.wb.internal.core.model.creation.ThisCreationSupport; import org.eclipse.wb.internal.core.model.creation.WrapperMethodControlCreationSupport; import org.eclipse.wb.internal.core.model.creation.factory.AbstractExplicitFactoryCreationSupport; import org.eclipse.wb.internal.core.model.creation.factory.InstanceFactoryInfo; import org.eclipse.wb.internal.core.model.description.ComponentDescription; import org.eclipse.wb.internal.core.model.description.ExposingRule; import org.eclipse.wb.internal.core.model.description.MethodDescription; import org.eclipse.wb.internal.core.model.description.factory.FactoryMethodDescription; import org.eclipse.wb.internal.core.model.description.helpers.ComponentDescriptionHelper; import org.eclipse.wb.internal.core.model.generation.GenerationUtils; import org.eclipse.wb.internal.core.model.generation.statement.StatementGenerator; import org.eclipse.wb.internal.core.model.order.ComponentOrder; import org.eclipse.wb.internal.core.model.order.ComponentOrderFirst; import org.eclipse.wb.internal.core.model.util.IJavaInfoRendering; import org.eclipse.wb.internal.core.model.util.ScriptUtils; import org.eclipse.wb.internal.core.model.variable.EmptyVariableSupport; import org.eclipse.wb.internal.core.model.variable.ExposedFieldVariableSupport; import org.eclipse.wb.internal.core.model.variable.ExposedPropertyVariableSupport; import org.eclipse.wb.internal.core.model.variable.LazyVariableSupport; import org.eclipse.wb.internal.core.model.variable.VariableSupport; import org.eclipse.wb.internal.core.utils.ast.AstEditor; import org.eclipse.wb.internal.core.utils.ast.AstNodeUtils; import org.eclipse.wb.internal.core.utils.ast.BodyDeclarationTarget; import org.eclipse.wb.internal.core.utils.ast.NodeTarget; import org.eclipse.wb.internal.core.utils.ast.StatementTarget; import org.eclipse.wb.internal.core.utils.check.Assert; import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils; import org.eclipse.wb.internal.core.utils.execution.RunnableEx; import org.eclipse.wb.internal.core.utils.external.ExternalFactoriesHelper; import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils; import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils; import org.eclipse.wb.internal.core.utils.state.EditorState; import org.eclipse.core.resources.IResource; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.apache.commons.lang.ArrayUtils; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Utilities for {@link JavaInfo}. * * @author scheglov_ke * @coverage core.model */ public class JavaInfoUtils { //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// private JavaInfoUtils() { } //////////////////////////////////////////////////////////////////////////// // // Access // //////////////////////////////////////////////////////////////////////////// /** * @return the {@link TypeDeclaration} that contains given {@link JavaInfo}. */ public static TypeDeclaration getTypeDeclaration(JavaInfo javaInfo) { return AstNodeUtils.getEnclosingType(javaInfo.getCreationSupport().getNode()); } /** * @return the {@link MethodDeclaration} that contains given {@link JavaInfo}. */ public static MethodDeclaration getMethodDeclaration(JavaInfo javaInfo) { return AstNodeUtils.getEnclosingMethod(javaInfo.getCreationSupport().getNode()); } /** * @return <code>true</code> if given {@link IField} is declared in top-level * {@link TypeDeclaration} of {@link JavaInfo}, directly or by one of its implemented * interfaces. */ public static boolean isLocalField(JavaInfo javaInfo, IField field) throws Exception { // prepare binding of top-level type ITypeBinding typeBinding; { TypeDeclaration typeDeclaration = getTypeDeclaration(javaInfo); typeBinding = AstNodeUtils.getTypeBinding(typeDeclaration); } // do check String declaringTypeName = field.getDeclaringType().getFullyQualifiedName(); return AstNodeUtils.isSuccessorOf(typeBinding, declaringTypeName); } /** * @return <code>true</code> if given {@link JavaInfo} created implicitly, with its host. */ public static boolean isImplicitlyCreated(JavaInfo javaInfo) { return javaInfo.getCreationSupport() instanceof IImplicitCreationSupport; } /** * Schedules save of {@link ICompilationUnit} of given {@link JavaInfo}.<br> * We should save {@link ICompilationUnit} after edit operation finished. */ public static void scheduleSave(final JavaInfo info) { DesignerPlugin.getStandardDisplay().asyncExec(new Runnable() { public void run() { ExecutionUtils.runLog(new RunnableEx() { public void run() throws Exception { info.getEditor().getModelUnit().getBuffer().save(null, false); } }); } }); } /** * Schedules opening given {@link ASTNode} in source view. */ public static void scheduleOpenNode(final JavaInfo javaInfo, final ASTNode node) { // Do in async because at this time changes may be not committed from ASTEditor to compilation unit. // This happens because this method is invoked in internal start/endEdit cycle. Display.getDefault().asyncExec(new Runnable() { public void run() { IDesignPageSite site = IDesignPageSite.Helper.getSite(javaInfo); site.openSourcePosition(node.getStartPosition()); } }); } //////////////////////////////////////////////////////////////////////////// // // Assertions // //////////////////////////////////////////////////////////////////////////// public static void assertIsNotDeleted(JavaInfo javaInfo) { Assert.isTrue(!javaInfo.isDeleted(), "Component is already deleted: %s", javaInfo); } //////////////////////////////////////////////////////////////////////////// // // Permissions // //////////////////////////////////////////////////////////////////////////// /** * @return <code>true</code> if given {@link JavaInfo} can be moved inside of its parent in some * way. Usually we just ask {@link CreationSupport#canReorder()}, however for example for * absolute layout we can not reorder exposed components, but we can move them (change * bounds). */ public static boolean canMove(final JavaInfo javaInfo) { // check, may be we can obtain external permission { final boolean[] forceMoveEnable = new boolean[1]; final boolean[] forceMoveDisable = new boolean[1]; ExecutionUtils.runLog(new RunnableEx() { public void run() throws Exception { javaInfo.getBroadcastJava().canMove(javaInfo, forceMoveEnable, forceMoveDisable); } }); if (forceMoveEnable[0]) { return true; } if (forceMoveDisable[0]) { return false; } } // ask CreationSupport return javaInfo.getCreationSupport().canReorder(); } /** * @return <code>true</code> if given {@link JavaInfo} can be moved into different parent. Here we * just ask {@link CreationSupport#canReparent()} and have this method only for symmetry * with {@link #canMove(JavaInfo)}. */ public static boolean canReparent(JavaInfo javaInfo) { return javaInfo.getCreationSupport().canReparent() && javaInfo.getAssociation().canDelete(); } //////////////////////////////////////////////////////////////////////////// // // Parameters // //////////////////////////////////////////////////////////////////////////// private static final String KEY_PARAMETER_PREFIX = "Instance-level parameter: "; /** * Sets the {@link JavaInfo} instance level parameter value, can be accesses later using * {@link #getParameter(JavaInfo, String)}. */ public static void setParameter(JavaInfo javaInfo, String name, String value) { String key = KEY_PARAMETER_PREFIX + name; javaInfo.putArbitraryValue(key, value); } /** * Returns the value of {@link JavaInfo} parameter.<br> * Usually returns just {@link ComponentDescription#getParameter(String)}, but factory components * we should also check this parameters in {@link FactoryMethodDescription}. * * @return the value of {@link JavaInfo} parameter. */ public static String getParameter(JavaInfo javaInfo, String name) { // try to key from JavaInfo instance { String key = KEY_PARAMETER_PREFIX + name; String value = (String) javaInfo.getArbitraryValue(key); if (value != null) { return value; } } // try to get from FactoryMethodDescription if (javaInfo.getCreationSupport() instanceof AbstractExplicitFactoryCreationSupport) { AbstractExplicitFactoryCreationSupport factoryCreationSupport = (AbstractExplicitFactoryCreationSupport) javaInfo .getCreationSupport(); FactoryMethodDescription factoryMethodDescription = factoryCreationSupport.getDescription(); String value = factoryMethodDescription.getParameter(name); if (value != null) { return value; } } // get from ComponentDescription return javaInfo.getDescription().getParameter(name); } /** * @return mapped {@link JavaInfo} parameters. */ public static Map<String, String> getParameters(JavaInfo javaInfo) { Map<String, String> parameters = Maps.newHashMap(); parameters.putAll(extractArbitraryParameters(javaInfo)); parameters.putAll(javaInfo.getDescription().getParameters()); return parameters; } /** * @return the {@link Map} of parameters set using {@link #setParameter(JavaInfo, String, String)} * . */ private static Map<String, String> extractArbitraryParameters(JavaInfo javaInfo) { Map<String, String> parameters = Maps.newHashMap(); for (Entry<Object, Object> arbitrary : javaInfo.getArbitraries().entrySet()) { Object key = arbitrary.getKey(); Object value = arbitrary.getValue(); if (key instanceof String && value instanceof String) { String stringKey = (String) key; if (stringKey.startsWith(KEY_PARAMETER_PREFIX)) { parameters.put(stringKey.substring(KEY_PARAMETER_PREFIX.length()), (String) value); } } } return parameters; } /** * Checks if {@link JavaInfo} has parameter with value <code>"true"</code>. */ public static boolean hasTrueParameter(JavaInfo javaInfo, String name) { String parameter = getParameter(javaInfo, name); return "true".equals(parameter); } //////////////////////////////////////////////////////////////////////////// // // Script // //////////////////////////////////////////////////////////////////////////// /** * If given {@link JavaInfo} has parameter with script, execute it with "model" and "object" * variables. */ public static Object executeScriptParameter(JavaInfo javaInfo, String scriptName) throws Exception { String script = getParameter(javaInfo, scriptName); if (script != null) { return executeScript(javaInfo, script); } return null; } /** * Execute script with "model" and "object" variables. */ public static Object executeScript(JavaInfo javaInfo, String script) throws Exception { ClassLoader classLoader = JavaInfoUtils.getClassLoader(javaInfo); Map<String, Object> variables = Maps.newHashMap(); variables.put("model", javaInfo); variables.put("object", javaInfo.getObject()); return ScriptUtils.evaluate(classLoader, script, variables); } //////////////////////////////////////////////////////////////////////////// // // Model creation // //////////////////////////////////////////////////////////////////////////// /** * @return new {@link JavaInfo} for given component {@link Class} name and {@link CreationSupport} * . */ public static JavaInfo createJavaInfo(AstEditor editor, String componentClassName, CreationSupport creationSupport) throws Exception { Class<?> componentClass = EditorState.get(editor).getEditorLoader().loadClass(componentClassName); return createJavaInfo(editor, componentClass, creationSupport); } /** * @return new {@link JavaInfo} for given component {@link Class} and {@link CreationSupport}. */ public static JavaInfo createJavaInfo(AstEditor editor, Class<?> componentClass, CreationSupport creationSupport) throws Exception { // prepare description ComponentDescription componentDescription = ComponentDescriptionHelper.getDescription(editor, componentClass); // create model return createJavaInfo(editor, componentDescription, creationSupport); } /** * @return new {@link JavaInfo} for given {@link ComponentDescription} and {@link CreationSupport} * . */ public static JavaInfo createJavaInfo(AstEditor editor, ComponentDescription componentDescription, CreationSupport creationSupport) throws Exception { // prepare constructor of model Constructor<?> modelConstructor; { Class<?> modelClass = componentDescription.getModelClass(); modelConstructor = modelClass.getConstructor( new Class[] { AstEditor.class, ComponentDescription.class, CreationSupport.class }); } // create model JavaInfo javaInfo = (JavaInfo) modelConstructor .newInstance(new Object[] { editor, componentDescription, creationSupport }); ObjectInfoUtils.setNewId(javaInfo); return javaInfo; } //////////////////////////////////////////////////////////////////////////// // // Exposed children // //////////////////////////////////////////////////////////////////////////// /** * Adds new exposed {@link JavaInfo}'s. */ public static void addExposedChildren(JavaInfo host, Class<?>[] exposedTypes) throws Exception { if (hasTrueParameter(host, "noExposedChildren")) { return; } addExposedChildred_Method(host, exposedTypes); addExposedChildred_Field(host, exposedTypes); // reorder added components buildExposedChildrenHierarchy(host); } /** * Adds new {@link JavaInfo}'s exposed using {@link PropertyDescriptor}'s. */ private static void addExposedChildred_Method(JavaInfo host, Class<?>[] exposedTypes) throws Exception { Object hostObject = host.getObject(); Assert.isNotNull(hostObject); // prepare property descriptors boolean includeProtected = shouldExposeProtectedMembers(host); List<PropertyDescriptor> propertyDescriptors = host.getDescription().getPropertyDescriptors(); // check all PropertyDescriptor's for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { Method setMethod = ReflectionUtils.getWriteMethod(propertyDescriptor); // prepare "getter" Method getMethod = ReflectionUtils.getReadMethod(propertyDescriptor); if (getMethod == null) { continue; } if (!includeProtected && ReflectionUtils.isProtected(getMethod)) { continue; } // check for return type { Class<?> returnType = getMethod.getReturnType(); if (!isExposedType(exposedTypes, returnType)) { continue; } } // check if this method can be used for exposing child if (!isEnabledExposeMethod(host, getMethod)) { continue; } // check that methods returns not "null" Object methodObject = null; { try { methodObject = getMethod.invoke(hostObject); if (methodObject == null) { continue; } } catch (Exception e) { continue; } } // check that component returned by method is bound to parent object if (!isExposeDisconnectedComponent(host, getMethod) && !areParentChild(host, methodObject)) { continue; } // check for infinite recursion if (isExposedRecursive(host, methodObject)) { continue; } // OK, add component addChildExposedByMethod(host, getMethod, setMethod, methodObject); } } /** * Adds new {@link JavaInfo} exposed by given {@link Method} as child of given <code>host</code>. */ public static JavaInfo addChildExposedByMethod(JavaInfo host, String getMethodName) throws Exception { // prepare "host" object Object hostObject = host.getObject(); Assert.isNotNull(hostObject); // prepare "method" object Method getMethod = ReflectionUtils.getMethod(hostObject.getClass(), getMethodName); Object methodObject = getMethod.invoke(hostObject); Assert.isNotNull(methodObject); // add exposed child return addChildExposedByMethod(host, getMethod, null, methodObject); } /** * Adds new {@link JavaInfo} exposed by given {@link Method} as child of given <code>host</code>. */ private static JavaInfo addChildExposedByMethod(JavaInfo host, Method getMethod, Method setMethod, Object methodObject) throws Exception { // check if already exposed if (isAlreadyExposedMethod(host, getMethod)) { return null; } // prepare elements AstEditor editor = host.getEditor(); boolean direct = getParentObject(methodObject) == host.getObject(); CreationSupport creationSupport = new ExposedPropertyCreationSupport(host, getMethod, setMethod, direct); // create child JavaInfo JavaInfo methodJavaInfo; if (InstanceFactoryInfo.isFactory(editor, getMethod.getReturnType())) { methodJavaInfo = InstanceFactoryInfo.createFactory(editor, getMethod.getReturnType(), creationSupport); } else { ComponentDescription componentDescription = ComponentDescriptionHelper.getDescription(editor, host.getDescription(), getMethod); methodJavaInfo = createJavaInfo(editor, componentDescription, creationSupport); } // configure JavaInfo methodJavaInfo.setVariableSupport(new ExposedPropertyVariableSupport(methodJavaInfo, host, getMethod)); methodJavaInfo.setAssociation(new ImplicitObjectAssociation(host)); // add new child addExposedJavaInfo(host, methodJavaInfo); // initialize methodJavaInfo.setObject(methodObject); return methodJavaInfo; } /** * @return <code>true</code> if given getter is already used to create exposed {@link JavaInfo}, * so should be ignored. If not used yet, we mark it as used and return <code>false</code> * . */ @SuppressWarnings("unchecked") private static boolean isAlreadyExposedMethod(JavaInfo host, Method getMethod) { String key = "JavaInfoUtils.alreadyExposed.Method"; Set<Method> alreadyExposed = (Set<Method>) host.getArbitraryValue(key); if (alreadyExposed == null) { alreadyExposed = Sets.newHashSet(); host.putArbitraryValue(key, alreadyExposed); } if (alreadyExposed.contains(getMethod)) { return true; } alreadyExposed.add(getMethod); return false; } /** * Adds new {@link JavaInfo}'s exposed using {@link PropertyDescriptor}'s. */ private static void addExposedChildred_Field(JavaInfo host, Class<?>[] exposedTypes) throws Exception { Object hostObject = host.getObject(); Assert.isNotNull(hostObject); // check all Field's boolean includeProtected = shouldExposeProtectedMembers(host); List<Field> fields = ReflectionUtils.getFields(hostObject.getClass()); for (Field field : fields) { // check modifier if (ReflectionUtils.isPrivate(field) || ReflectionUtils.isPackagePrivate(field)) { continue; } if (ReflectionUtils.isProtected(field) && !includeProtected) { continue; } // check for type { Class<?> fieldType = field.getType(); if (!isExposedType(exposedTypes, fieldType)) { continue; } } // check if this field can be used for exposing child if (!isEnabledExposeField(host, field)) { continue; } // check that methods returns not "null" Object fieldObject; { fieldObject = field.get(hostObject); if (fieldObject == null) { continue; } } // check that component returned by method is bound to parent object if (!areParentChild(host, fieldObject)) { continue; } // check for infinite recursion if (isExposedRecursive(host, fieldObject)) { continue; } // OK, add component addChildExposedByField(host, field, fieldObject); } } /** * Adds new {@link JavaInfo} exposed by given {@link Field} as child of given <code>host</code>. */ private static JavaInfo addChildExposedByField(JavaInfo host, Field field, Object fieldObject) throws Exception { // check if already exposed if (isAlreadyExposedField(host, field)) { return null; } // prepare elements AstEditor editor = host.getEditor(); boolean direct = getParentObject(fieldObject) == host.getObject(); CreationSupport creationSupport = new ExposedFieldCreationSupport(host, field, direct); // create child JavaInfo JavaInfo fieldJavaInfo; ComponentDescription componentDescription = ComponentDescriptionHelper.getDescription(editor, field.getType()); fieldJavaInfo = createJavaInfo(editor, componentDescription, creationSupport); // configure JavaInfo fieldJavaInfo.setVariableSupport(new ExposedFieldVariableSupport(fieldJavaInfo, host, field)); fieldJavaInfo.setAssociation(new ImplicitObjectAssociation(host)); // add new child addExposedJavaInfo(host, fieldJavaInfo); // initialize fieldJavaInfo.setObject(fieldObject); return fieldJavaInfo; } /** * @return <code>true</code> if given {@link Field} is already used to create exposed * {@link JavaInfo}, so should be ignored. If not used yet, we mark it as used and return * <code>false</code>. */ @SuppressWarnings("unchecked") private static boolean isAlreadyExposedField(JavaInfo host, Field field) { String key = "JavaInfoUtils.alreadyExposed.Field"; Set<Field> alreadyExposed = (Set<Field>) host.getArbitraryValue(key); if (alreadyExposed == null) { alreadyExposed = Sets.newHashSet(); host.putArbitraryValue(key, alreadyExposed); } if (alreadyExposed.contains(field)) { return true; } alreadyExposed.add(field); return false; } /** * Adds exposed {@link JavaInfo} to its host. */ private static void addExposedJavaInfo(JavaInfo host, JavaInfo exposed) throws Exception { for (HierarchyProvider provider : getHierarchyProviders()) { provider.add(host, exposed); if (exposed.getParent() != null) { break; } } if (exposed.getParent() == null) { host.addChild(exposed); } } /** * @return <code>true</code> if given {@link Method} can expose child. */ private static boolean isEnabledExposeMethod(JavaInfo host, Method getMethod) { // check filters for (ExposingRule rule : host.getDescription().getExposingRules()) { Boolean filter = rule.filter(getMethod); if (filter != null) { if (filter.booleanValue()) { return true; } else { return false; } } } // if method of "this" JavaInfo overrides this method, ignore it to avoid binary execution flow if (host.getCreationSupport() instanceof ThisCreationSupport) { TypeDeclaration typeDeclaration = getTypeDeclaration(host); String signature = ReflectionUtils.getMethodSignature(getMethod); if (AstNodeUtils.getMethodBySignature(typeDeclaration, signature) != null) { return false; } } // if no rule that forbids this method, then allow exposing return true; } /** * @return <code>true</code> if given {@link Field} can expose child. */ private static boolean isEnabledExposeField(JavaInfo host, Field field) { // check filters for (ExposingRule rule : host.getDescription().getExposingRules()) { Boolean filter = rule.filter(field); if (filter != null) { if (filter.booleanValue()) { return true; } else { return false; } } } // if no rule that forbids this field, then allow exposing return true; } /** * @return <code>true</code> if current form can see protected members of "host". */ private static boolean shouldExposeProtectedMembers(JavaInfo host) { // if subclass if (host.getCreationSupport() instanceof ThisCreationSupport) { return true; } // if same package { String unitPackageName = AstNodeUtils.getPackageName(host.getEditor().getAstUnit()); String hostPackageName = CodeUtils.getPackage(host.getDescription().getComponentClass().getName()); if (unitPackageName.equals(hostPackageName)) { return true; } } // no return false; } /** * @return <code>true</code> if given {@link Class} is assignable to one of the given exposed * types. */ private static boolean isExposedType(Class<?>[] exposedTypes, Class<?> candidate) { for (Class<?> exposedType : exposedTypes) { if (exposedType.isAssignableFrom(candidate)) { return true; } } return false; } /** * @return <code>true</code> if given {@link Method} should expose component even if this * component is not connected to the host object. */ private static boolean isExposeDisconnectedComponent(JavaInfo host, Method getMethod) throws Exception { String signature = ReflectionUtils.getMethodSignature(getMethod); MethodDescription methodDescription = host.getDescription().getMethod(signature); return methodDescription != null && methodDescription.hasTrueTag("exposeDisconnectedComponent"); } /** * @return <code>true</code> if given {@link Object} will cause infinite recursive exposing. */ private static boolean isExposedRecursive(JavaInfo host, Object methodObject) { return host.getObject() == methodObject; } /** * @return <code>true</code> if given {@link JavaInfo} is not direct child of its parent, i.e. it * is child of some inner (not exposed) container. */ public static boolean isIndirectlyExposed(JavaInfo javaInfo) { CreationSupport creationSupport = javaInfo.getCreationSupport(); if (creationSupport instanceof IExposedCreationSupport) { return !((IExposedCreationSupport) creationSupport).isDirect(); } if (creationSupport instanceof IWrapperControlCreationSupport) { JavaInfo wrapper = ((IWrapperControlCreationSupport) creationSupport).getWrapperInfo(); return isIndirectlyExposed(wrapper); } return false; } //////////////////////////////////////////////////////////////////////////// // // Sorting: exposed // //////////////////////////////////////////////////////////////////////////// /** * Interface for retrieving parent/child information using widget instances of specific toolkit. * Used to build hierarchy of exposed children. * * @author scheglov_ke * @author mitin_aa */ public static class HierarchyProvider { /** * @return all aliases of given component object (including given object), may be * <code>null</code>, if no aliases. */ public Object[] getAliases(Object object) throws Exception { return null; } /** * @return the parent object for given object. */ public Object getParentObject(Object object) throws Exception { return null; } /** * @return the children of given toolkit object. * * @param object * the object to get children from */ public Object[] getChildrenObjects(Object object) throws Exception { return ArrayUtils.EMPTY_OBJECT_ARRAY; } /** * Can associate exposed {@link JavaInfo} with its host. * <p> * For example in RCP we need to create intermediate Control for exposed Viewer. */ public void add(JavaInfo host, JavaInfo exposed) throws Exception { } } //////////////////////////////////////////////////////////////////////////// // // Build exposed children hierarchy // //////////////////////////////////////////////////////////////////////////// /** * For custom component it is possible that exposed component is a child of other exposed * component, but from custom component point of view this looks as two independent components. * This method restores such logical hierarchy. */ private static void buildExposedChildrenHierarchy(JavaInfo host) throws Exception { // prepare map (object -> child) final Map<Object, JavaInfo> objectToChild = Maps.newHashMap(); for (JavaInfo child : host.getChildrenJava()) { objectToChild.put(child.getObject(), child); } // prepare sorted list of children, so we will add them to the logical parents in correct order final List<JavaInfo> sortedChildren; { sortedChildren = Lists.newArrayList(); fillChildren(sortedChildren, host.getObject(), objectToChild); } // sort children in host JavaInfo (to reorder children that have host as logical parent) // only exposed children should be sorted Collections.sort(host.getChildren(), new Comparator<ObjectInfo>() { public int compare(ObjectInfo o1, ObjectInfo o2) { // ignore not exposed { boolean exposed_1 = isExposed(o1); boolean exposed_2 = isExposed(o2); if (!exposed_1 || !exposed_2) { return 0; } } // two exposed return sortedChildren.indexOf(o1) - sortedChildren.indexOf(o2); } private boolean isExposed(ObjectInfo o) { if (o instanceof JavaInfo) { JavaInfo javaInfo = (JavaInfo) o; return javaInfo.getCreationSupport() instanceof IExposedCreationSupport; } return false; } }); // find logical parent for each exposed child for (JavaInfo child : sortedChildren) { if (child.getCreationSupport() instanceof IExposedCreationSupport) { // go up along the hierarchy Object parentObject = getParentObject(child.getObject()); for (; parentObject != null; parentObject = getParentObject(parentObject)) { JavaInfo parent = objectToChild.get(parentObject); if (parent != null) { // move child to the logical parent host.removeChild(child); parent.addChild(child); // OK, we found logical parent break; } } } } } /** * @return the parent object of given object according to toolkit which object belongs to * * @param childObject * the object to get parent for */ private static Object getParentObject(Object childObject) throws Exception { for (HierarchyProvider provider : getHierarchyProviders()) { Object parentObject = provider.getParentObject(childObject); if (parentObject != null) { return parentObject; } } return null; } /** * @return <code>true</code> if given objects are parent/child. */ private static boolean areParentChild(JavaInfo host, Object child) throws Exception { Object[] requiredParents = getComponentObjects(host); for (Object requiredParent : requiredParents) { // check by parent if (areParentChild(requiredParent, child)) { return true; } // check in children for (HierarchyProvider provider : getHierarchyProviders()) { Object[] children = provider.getChildrenObjects(requiredParent); if (ArrayUtils.contains(children, child)) { return true; } } } return false; } /** * @return <code>true</code> if given objects are parent/child. */ private static boolean areParentChild(Object requiredParent, Object child) throws Exception { for (Object parent = child; parent != null; parent = getParentObject(parent)) { if (parent == requiredParent) { return true; } } return false; } /** * Traverse hierarchy starting from given objects and fill {@link List} of {@link JavaInfo} models * in top-down order. * * @param children * the {@link List} to add ordered {@link JavaInfo} models * @param object * the current object to fill models from * @param objectToChild * the {@link Map} to convert objects into models */ private static void fillChildren(List<JavaInfo> children, Object object, Map<Object, JavaInfo> objectToChild) throws Exception { // add model for current object { JavaInfo child = objectToChild.get(object); if (child != null) { children.add(child); } } // prepare children objects List<Object> childrenObjects = Lists.newArrayList(); for (HierarchyProvider provider : getHierarchyProviders()) { Collections.addAll(childrenObjects, provider.getChildrenObjects(object)); } // process children objects for (Object childOfChild : childrenObjects) { fillChildren(children, childOfChild, objectToChild); } } /** * @return the instances of {@link HierarchyProvider}. */ private static List<HierarchyProvider> getHierarchyProviders() { return ExternalFactoriesHelper.getElementsInstances(HierarchyProvider.class, "org.eclipse.wb.core.componentsHierarchyProviders", "provider"); } //////////////////////////////////////////////////////////////////////////// // // Binary hierarchy binding // //////////////////////////////////////////////////////////////////////////// /** * Binds any not bound yet components to the given root {@link JavaInfo} or any of its children. */ public static void bindBinaryComponents(List<JavaInfo> components) throws Exception { List<JavaInfo> reverseComponents = ImmutableList.copyOf(components).reverse(); // prepare map (object -> JavaInfo) final Map<Object, JavaInfo> objectToModel; { objectToModel = Maps.newHashMap(); for (JavaInfo component : components) { component.accept(new ObjectInfoVisitor() { @Override public void endVisit(ObjectInfo objectInfo) throws Exception { if (objectInfo instanceof JavaInfo) { JavaInfo javaInfo = (JavaInfo) objectInfo; for (Object componentObject : getComponentObjects(javaInfo)) { objectToModel.put(componentObject, javaInfo); } } } }); } } // prepare toolkit objects for ALL hierarchies List<Object> objects; { objects = Lists.newArrayList(); for (JavaInfo component : components) { if (component.getParent() == null) { for (Object componentObject : getComponentObjects(component)) { Assert.isNotNull(componentObject, "No object for component: %s", component); fillToolkitObjects(objects, componentObject); } } } } // bind not bound components for (JavaInfo component : components) { if (component.getParent() == null) { bindBinaryComponent_toParent(objectToModel, objects, component); } } // check still not bound component, it may be alias for some other component for (JavaInfo component : reverseComponents) { if (component.getParent() == null) { bindBinaryComponent_asAlias(components, component); } } // check still not bound component, flow to depth for (JavaInfo component : reverseComponents) { if (component.getParent() == null && component.getDescription().hasTrueParameter("bindBinary.toDepth")) { bindBinaryComponent_toDepth(objectToModel, component); } } } /** * Binds single binary component to parent. */ private static void bindBinaryComponent_toParent(Map<Object, JavaInfo> objectToModel, List<Object> objects, JavaInfo component) throws Exception { for (Object object : getComponentObjects(component)) { int objectIndex = objects.indexOf(object); // go up along the hierarchy Object parentObject = getParentObject(object); for (; parentObject != null; parentObject = getParentObject(parentObject)) { JavaInfo parent = objectToModel.get(parentObject); if (parent != null) { boolean added = false; // try to add before some existing "child" nextChild: for (JavaInfo child : parent.getChildrenJava()) { for (Object childObject : getComponentObjects(child)) { int childIndex = objects.indexOf(childObject); if (objectIndex < childIndex) { parent.addChild(component, child); added = true; break nextChild; } } } // if not added yet, add as the last "child" if (!added) { parent.addChild(component); } // set association component.setAssociation(new UnknownAssociation()); // OK, we found logical parent return; } } } } /** * Binds single binary component to its alias (but not to itself). */ private static void bindBinaryComponent_asAlias(List<JavaInfo> components, JavaInfo component) throws Exception { for (Object object : getComponentObjects(component)) { for (JavaInfo otherComponent : components) { for (Object otherObject : getComponentObjects(otherComponent)) { boolean otherIsSameOrChild = otherComponent == component || component.isParentOf(otherComponent); if (otherObject == object && !otherIsSameOrChild) { List<AbstractComponentInfo> children = otherComponent .getChildren(AbstractComponentInfo.class); JavaInfo nextChild = !children.isEmpty() ? children.get(0) : null; otherComponent.addChild(component, nextChild); component.setAssociation(new UnknownAssociation()); return; } } } } } /** * @return {@link Object}'s that represent given component. Usually this is just single object, * but in GWT components has two presentations: <code>UIObject</code> and its * <code>Element</code>. */ private static Object[] getComponentObjects(JavaInfo component) throws Exception { Object object = getComponentObject0(component); if (object == null) { return ArrayUtils.EMPTY_OBJECT_ARRAY; } for (HierarchyProvider provider : getHierarchyProviders()) { Object[] objects = provider.getAliases(object); if (objects != null) { return objects; } } return new Object[] { object }; } /** * @return the {@link AbstractComponentInfo#getComponentObject()} or {@link JavaInfo#getObject()}. */ private static Object getComponentObject0(JavaInfo component) { return component instanceof AbstractComponentInfo ? ((AbstractComponentInfo) component).getComponentObject() : component.getObject(); } /** * Traverse hierarchy starting from given objects and fill {@link List} with toolkit * {@link Object}'s in top-down order. * * @param objects * the {@link List} to add ordered toolkit objects. * @param object * the current toolkit object. */ private static void fillToolkitObjects(List<Object> objects, Object object) throws Exception { // add current object objects.add(object); // prepare children objects List<Object> children = Lists.newArrayList(); for (HierarchyProvider provider : getHierarchyProviders()) { Collections.addAll(children, provider.getChildrenObjects(object)); } // process children objects for (Object child : children) { fillToolkitObjects(objects, child); } } /** * Binds single binary component to parent using flow from root to depth. */ private static void bindBinaryComponent_toDepth(Map<Object, JavaInfo> objectToModel, JavaInfo component) throws Exception { Object[] objects = getComponentObjects(component); for (Object object : objects) { bindBinaryComponent_toDepth(objectToModel, component, object); } } private static void bindBinaryComponent_toDepth(Map<Object, JavaInfo> objectToModel, JavaInfo parentInfo, Object object) throws Exception { for (HierarchyProvider provider : getHierarchyProviders()) { Object[] children = provider.getChildrenObjects(object); for (Object child : children) { JavaInfo childInfo = objectToModel.get(child); if (childInfo != null) { if (childInfo.getParent() == null) { parentInfo.addChild(childInfo); childInfo.setAssociation(new UnknownAssociation()); } bindBinaryComponent_toDepth(objectToModel, childInfo, child); } else { bindBinaryComponent_toDepth(objectToModel, parentInfo, child); } } } } //////////////////////////////////////////////////////////////////////////// // // Adding new JavaInfo // //////////////////////////////////////////////////////////////////////////// /** * Adds new component to container before some existing component. * * @param component * the component to add. * @param associationObject * the container {@link AssociationObject}. * @param container * the parent for adding component. * @param nextComponent * the component before which new component should be added, may be <code>null</code>, so * new component will be added as last child of parent. */ public static void add(JavaInfo component, AssociationObject associationObject, JavaInfo container, JavaInfo nextComponent) throws Exception { VariableSupport variableSupport = GenerationUtils.getVariableSupport(component); StatementGenerator statementGenerator = GenerationUtils.getStatementGenerator(component); add(component, variableSupport, statementGenerator, associationObject, container, nextComponent); } /** * Adds new component to container into given {@link StatementTarget}. * * @param component * the component to add. * @param associationObject * the container {@link AssociationObject}. * @param container * the parent for adding component. * @param target * the {@link StatementTarget} to add component to. */ public static void addTarget(JavaInfo component, AssociationObject associationObject, JavaInfo container, StatementTarget target) throws Exception { addTarget(component, associationObject, container, null, target); } public static void addTarget(JavaInfo component, AssociationObject associationObject, JavaInfo container, JavaInfo nextComponent, StatementTarget target) throws Exception { VariableSupport variableSupport = GenerationUtils.getVariableSupport(component); StatementGenerator statementGenerator = GenerationUtils.getStatementGenerator(component); add(component, variableSupport, statementGenerator, associationObject, container, nextComponent, target); } /** * Adds new component to container before some existing component. * * @param component * the component to add. * @param variableSupport * the {@link VariableSupport} for new component. * @param statementGenerator * the {@link StatementGenerator} for new component. * @param associationObject * the container {@link AssociationObject}. * @param container * the parent for adding component. * @param nextComponent * the component before which new component should be added, may be <code>null</code>, so * new component will be added as last child of parent. */ public static void add(JavaInfo component, VariableSupport variableSupport, StatementGenerator statementGenerator, AssociationObject associationObject, JavaInfo container, JavaInfo nextComponent) throws Exception { nextComponent = getNextComponent_useComponentOrder(component, container, nextComponent); StatementTarget target = getTarget(container, component, nextComponent); add(component, variableSupport, statementGenerator, associationObject, container, nextComponent, target); } /** * @return the actual "nextComponent" that should be used to conform "component" * {@link ComponentOrder} and {@link ComponentOrder}-s of existing children. */ private static JavaInfo getNextComponent_useComponentOrder(JavaInfo component, JavaInfo container, JavaInfo nextComponent) throws Exception { ComponentOrder order = component.getDescription().getOrder(); // may be this "component" wants to be first if (nextComponent == null) { nextComponent = order.getNextComponent_whenLast(component, container); } // may be other child wants to be last if (nextComponent == null) { for (JavaInfo parentChild : container.getChildrenJava()) { if (!parentChild.getDescription().getOrder().canBeBefore(component)) { return parentChild; } } } // no changes return nextComponent; } /** * Adds new component to container as first child. * * @param component * the component to add. * @param associationObject * the container {@link AssociationObject}. * @param container * the parent for adding component. */ public static void addFirst(JavaInfo component, AssociationObject associationObject, JavaInfo container) throws Exception { ComponentOrder order = ComponentOrderFirst.INSTANCE; JavaInfo nextComponent = order.getNextComponent_whenLast(component, container); StatementTarget target = getTarget(container, component, nextComponent); // VariableSupport variableSupport = GenerationUtils.getVariableSupport(component); StatementGenerator statementGenerator = GenerationUtils.getStatementGenerator(component); add(component, variableSupport, statementGenerator, associationObject, container, nextComponent, target); } /** * Adds new component to container before some existing component. * * @param component * the component to add. * @param variableSupport * the {@link VariableSupport} for new component. * @param statementGenerator * the {@link StatementGenerator} for new component. * @param associationObject * the container {@link AssociationObject}. * @param container * the parent for adding component. * @param nextComponent * the component before which new component should be added, may be <code>null</code>, so * new component will be added as last child of parent. * @param target * the {@link StatementTarget} where {@link Statement}'s (more precise - association) of * new component should be added. */ public static void add(JavaInfo component, VariableSupport variableSupport, StatementGenerator statementGenerator, AssociationObject associationObject, JavaInfo container, JavaInfo nextComponent, StatementTarget target) throws Exception { container.getBroadcastJava().addBefore(container, component); // setup hierarchy container.addChild(component, nextComponent); // set variable support component.setVariableSupport(variableSupport); // add component and association using StatementGenerator Association association = createAssociation(container, component, associationObject); statementGenerator.add(component, target, association); // container.getBroadcastJava().addAfter(container, component); } /** * When container does not have special {@link Association}, then only {@link Association} from * component should be used. * <p> * When container has special {@link Association} and it is required, then {@link Association} * from component should be mixed with it in {@link CompoundAssociation}. */ private static Association createAssociation(JavaInfo container, JavaInfo component, AssociationObject associationObject) throws Exception { associationObject = getNotNullAssociationObject(associationObject); Association componentAssociation = component.getCreationSupport().getAssociation(); Association containerAssociation = associationObject.getAssociation(); // try to mix with container association if (componentAssociation != null) { if (associationObject.isRequired()) { return new CompoundAssociation(componentAssociation, containerAssociation); } return componentAssociation; } // use container association Assert.isNotNull(containerAssociation, "No special container association and no component creation association for %s and %s", container, component); return containerAssociation; } /** * @return the given not null {@link AssociationObject} or {@link AssociationObject} with * <code>null</code> as {@link Association}. */ private static AssociationObject getNotNullAssociationObject(AssociationObject associationObject) { if (associationObject == null) { associationObject = new AssociationObject(null, false); } return associationObject; } //////////////////////////////////////////////////////////////////////////// // // Move // //////////////////////////////////////////////////////////////////////////// /** * Internal interface for providing {@link StatementTarget} during move. */ public interface IMoveTargetProvider { void add() throws Exception; void move() throws Exception; StatementTarget getTarget() throws Exception; } /** * Moves component to new container.<br> * Move can be inner (i.e. move association) or adding from other container. * * @param component * the component to move. * @param associationObject * the container {@link AssociationObject}. * @param newParent * the parent for adding component, may be old parent (so, we just move inside it), or * new one, so we reparent component. * @param nextComponent * the component before which new component should be added, may be <code>null</code>, so * new component will be added as last child of parent. */ public static void move(JavaInfo component, AssociationObject associationObject, JavaInfo newParent, JavaInfo nextComponent) throws Exception { // pre-condition: move component before itself, ignore if (nextComponent == component) { return; } // do move nextComponent = getNextComponent_useComponentOrder(component, newParent, nextComponent); move0(component, associationObject, newParent, nextComponent); } private static void move0(final JavaInfo component, final AssociationObject associationObject, final JavaInfo newParent, final JavaInfo nextComponent) throws Exception { IMoveTargetProvider targetProvider = new IMoveTargetProvider() { public void add() throws Exception { newParent.addChild(component, nextComponent); } public void move() throws Exception { newParent.moveChild(component, nextComponent); } public StatementTarget getTarget() throws Exception { return JavaInfoUtils.getTarget(newParent, component, nextComponent); } }; moveProvider(component, associationObject, newParent, targetProvider); } /** * Moves component: inside of same container, or to new container. */ public static void moveTarget(final JavaInfo component, AssociationObject associationObject, final JavaInfo newParent, final JavaInfo nextComponent, final StatementTarget target) throws Exception { IMoveTargetProvider targetProvider = new IMoveTargetProvider() { public void add() throws Exception { newParent.addChild(component, nextComponent); } public void move() throws Exception { newParent.moveChild(component, nextComponent); } public StatementTarget getTarget() throws Exception { return target; } }; moveProvider(component, associationObject, newParent, targetProvider); } /** * Moves component to new container.<br> * Move can be inner (i.e. move association) or adding from other container. * * @param component * the component to move. * @param associationObject * the container {@link AssociationObject}. * @param newParent * the parent for adding component, may be old parent (so, we just move inside it), or * new one, so we reparent component. * @param targetProvider * the {@link IMoveTargetProvider} that is used to separate move operation from target * {@link Statement} and position in {@link JavaInfo} children. */ public static void moveProvider(JavaInfo component, AssociationObject associationObject, JavaInfo newParent, IMoveTargetProvider targetProvider) throws Exception { Association oldAssociation = component.getAssociation(); associationObject = getNotNullAssociationObject(associationObject); Association newAssociation = associationObject.getAssociation(); boolean newAssociationRequired = associationObject.isRequired(); // ObjectInfo oldParent = component.getParent(); boolean isReparenting = oldParent != newParent; newParent.getBroadcastJava().moveBefore0(component, oldParent, newParent); newParent.getBroadcastJava().moveBefore(component, oldParent, newParent); // remove association // we do this now to avoid possible using association node as target if (isReparenting || newAssociationRequired) { materializeVariable(component); oldAssociation.remove(); } else if (oldAssociation instanceof InvocationChildArrayAssociation || oldAssociation instanceof InvocationChildEllipsisAssociation) { // support array VariableSupport variableSupport = component.getVariableSupport(); if (variableSupport instanceof EmptyVariableSupport) { ((EmptyVariableSupport) variableSupport).materialize(); } oldAssociation.remove(); newAssociationRequired = true; } // move component ASTNode's, prepare statements target StatementTarget componentTarget; { componentTarget = targetProvider.getTarget(); component.getVariableSupport().ensureInstanceReadyAt(componentTarget); } // move/reparent component if (isReparenting) { oldParent.removeChild(component); targetProvider.add(); } else { targetProvider.move(); } // now, when all moving are done, prepare association target StatementTarget associationTarget = component.getVariableSupport().getAssociationTarget(componentTarget); // update association if (isReparenting || newAssociationRequired) { if (component.getAssociation() == null) { newAssociation.add(component, associationTarget, null); } else { // check, may be we need to add one more association if (newAssociation != null) { CompoundAssociation compoundAssociation = new CompoundAssociation(component.getAssociation()); compoundAssociation.add(newAssociation); component.setAssociation(compoundAssociation); } // ask association about update to reflect new parent component.getAssociation().setParent(newParent); } } else { // for "lazy creation" - move association // for other variables - it will be moved with other component statements if (component.getVariableSupport() instanceof LazyVariableSupport) { component.getAssociation().move(associationTarget); } } // end move operation newParent.getBroadcastJava().moveAfter(component, oldParent, newParent); } //////////////////////////////////////////////////////////////////////////// // // Target // //////////////////////////////////////////////////////////////////////////// /** * @return new {@link StatementTarget} where {@link Statement}'s for new {@link JavaInfo} should * be added to place them before given <code>nextChild</code>. * * @param parent * the parent to which {@link JavaInfo} should be added. * @param child * the child that will be added. * @param nextChild * the {@link JavaInfo} before which next {@link JavaInfo} should be added, can be * <code>null</code> that means that new {@link JavaInfo} should be added as last child * of parent. */ public static StatementTarget getTarget(JavaInfo parent, JavaInfo child, JavaInfo nextChild) throws Exception { return new ChildTargetCalculator(parent, child, nextChild).getTarget(); } /** * @return new {@link StatementTarget} where {@link Statement}'s should be added to place them * before given <code>nextChild</code>. * * @param parent * the parent to which {@link JavaInfo} should be added. * @param nextChild * the {@link JavaInfo} before which next {@link JavaInfo} should be added, can be * <code>null</code> that means that new {@link JavaInfo} should be added as last child * of parent. */ public static StatementTarget getTarget(JavaInfo parent, JavaInfo nextChild) throws Exception { return getTarget(parent, null, nextChild); } /** * @return new {@link StatementTarget} where {@link Statement}'s should be added. * * @param parent * the parent to which {@link JavaInfo} should be added. */ public static StatementTarget getTarget(JavaInfo parent) throws Exception { return getTarget(parent, null, null); } /** * Ensures that given {@link JavaInfo} has some "real" {@link VariableSupport}. For example we * materialize {@link EmptyVariableSupport} because we may need to reference creation/association * {@link Statement}. */ public static void materializeVariable(JavaInfo javaInfo) throws Exception { if (javaInfo.getVariableSupport() instanceof EmptyVariableSupport) { EmptyVariableSupport variableSupport = (EmptyVariableSupport) javaInfo.getVariableSupport(); variableSupport.materialize(); } } //////////////////////////////////////////////////////////////////////////// // // Creation sequence // //////////////////////////////////////////////////////////////////////////// /** * Sorts {@link JavaInfo}'s by the time when they are created on execution flow. */ public static void sortComponentsByFlow(final List<JavaInfo> components) { if (components.isEmpty()) { return; } // prepare ExecutionFlowDescription ExecutionFlowDescription flowDescription; { JavaInfo someComponent = components.get(0); flowDescription = getState(someComponent).getFlowDescription(); } // prepare new List, with components in execution flow order final List<JavaInfo> sortedComponents = Lists.newArrayList(); ExecutionFlowUtils.visit(new VisitingContext(true), flowDescription, new ExecutionFlowFrameVisitor() { @Override public void postVisit(ASTNode node) { for (JavaInfo component : components) { if (component.getCreationSupport().getNode() == node) { sortedComponents.add(component); break; } } } }); // sort original List Collections.sort(components, new Comparator<JavaInfo>() { public int compare(JavaInfo o1, JavaInfo o2) { int index_1 = sortedComponents.indexOf(o1); int index_2 = sortedComponents.indexOf(o2); return index_1 - index_2; } }); } /** * Sorts {@link ASTNode}'s by the time when they are visited on execution flow. {@link ASTNode}'s * not included into execution flow should be removed. * * @param onEnter * is <code>true</code> if {@link ASTNode} considered as visited on enter, use * <code>false</code> to consider as visited on exit. */ public static void sortNodesByFlow(ExecutionFlowDescription flowDescription, final boolean onEnter, final List<? extends ASTNode> nodes) { final List<ASTNode> sortedNodes = Lists.newArrayList(); ExecutionFlowUtils.visit(new VisitingContext(true), flowDescription, new ExecutionFlowFrameVisitor() { @Override public boolean enterFrame(ASTNode node) { if (onEnter) { addSortedNode(node); } return super.enterFrame(node); } @Override public void leaveFrame(ASTNode node) { if (!onEnter) { addSortedNode(node); } } @Override public void preVisit(ASTNode node) { if (onEnter) { addSortedNode(node); } } @Override public void postVisit(ASTNode node) { if (!onEnter) { addSortedNode(node); } } private void addSortedNode(ASTNode node) { if (nodes.contains(node)) { sortedNodes.add(node); } } }); // remove nodes that are not visited on execution flow for (Iterator<? extends ASTNode> I = nodes.iterator(); I.hasNext();) { ASTNode node = I.next(); if (!sortedNodes.contains(node)) { I.remove(); } } // sort original List Collections.sort(nodes, new Comparator<ASTNode>() { public int compare(ASTNode o1, ASTNode o2) { int index_1 = sortedNodes.indexOf(o1); int index_2 = sortedNodes.indexOf(o2); return index_1 - index_2; } }); } /** * @return <code>true</code> if {@link JavaInfo} is already created at/before location specified * by {@link NodeTarget}. */ public static boolean isCreatedAtTarget(JavaInfo javaInfo, NodeTarget target) { // special case: JavaInfo with constructor as node { ASTNode node = javaInfo.getCreationSupport().getNode(); if (node instanceof MethodDeclaration) { MethodDeclaration methodDeclaration = (MethodDeclaration) node; if (methodDeclaration.isConstructor()) { return true; } } } // StatementTarget { StatementTarget statementTarget = target.getStatementTarget(); if (statementTarget != null) { return isCreatedAt(javaInfo, statementTarget); } } // BodyDeclarationTarget { BodyDeclarationTarget bodyDeclarationTarget = target.getBodyDeclarationTarget(); return isCreatedAt(javaInfo, bodyDeclarationTarget); } } /** * @return <code>true</code> if {@link JavaInfo} is already created at location specified by * {@link StatementTarget}. */ private static boolean isCreatedAt(JavaInfo javaInfo, StatementTarget target) { ExecutionFlowDescription flowDescription = getState(javaInfo).getFlowDescription(); ASTNode javaInfoNode = javaInfo.getCreationSupport().getNode(); // Statement { Statement statement = target.getStatement(); if (statement != null) { List<ASTNode> nodes = Lists.newArrayList(statement, javaInfoNode); sortNodesByFlow(flowDescription, target.isBefore(), nodes); return nodes.get(0) == javaInfoNode; } } // Block { Block block = target.getBlock(); List<ASTNode> nodes = Lists.newArrayList(block, javaInfoNode); sortNodesByFlow(flowDescription, target.isBefore(), nodes); return nodes.get(0) == javaInfoNode; } } /** * @return <code>true</code> if {@link JavaInfo} is already created at location specified by * {@link BodyDeclarationTarget}. */ private static boolean isCreatedAt(JavaInfo javaInfo, BodyDeclarationTarget target) { ExecutionFlowDescription flowDescription = getState(javaInfo).getFlowDescription(); ASTNode javaInfoNode = javaInfo.getCreationSupport().getNode(); // BodyDeclaration { BodyDeclaration bodyDeclaration = target.getDeclaration(); if (bodyDeclaration != null) { List<ASTNode> nodes = Lists.newArrayList(bodyDeclaration, javaInfoNode); sortNodesByFlow(flowDescription, target.isBefore(), nodes); return nodes.get(0) == javaInfoNode; } } // TypeDeclaration { TypeDeclaration typeDeclaration = target.getType(); List<ASTNode> nodes = Lists.newArrayList(typeDeclaration, javaInfoNode); sortNodesByFlow(flowDescription, target.isBefore(), nodes); return nodes.get(0) == javaInfoNode; } } /** * @return the {@link StatementTarget} such that all given {@link JavaInfo} are created at this * target, so can be referenced. */ public static StatementTarget getStatementTarget_whenAllCreated(List<? extends JavaInfo> components) throws Exception { Assert.isTrue(!components.isEmpty(), "Can not provide target for empty components list."); // prepare target after last component NodeTarget nodeTarget_afterLastComponent; { List<JavaInfo> componentsCopy = Lists.newArrayList(components); sortComponentsByFlow(componentsCopy); JavaInfo lastComponent = componentsCopy.get(componentsCopy.size() - 1); nodeTarget_afterLastComponent = getNodeTarget_afterCreation(lastComponent); } // convert NodeTarget into StatementTarget StatementTarget statementTarget = nodeTarget_afterLastComponent.getStatementTarget(); if (statementTarget != null) { return statementTarget; } // probably all components created in fields, so use "this" target JavaInfo rootJava = components.get(0).getRootJava(); if (rootJava.getCreationSupport() instanceof ThisCreationSupport) { return rootJava.getVariableSupport().getStatementTarget(); } // use "flow" method ExecutionFlowDescription flowDescription = getState(rootJava).getFlowDescription(); MethodDeclaration startMethod = flowDescription.getStartMethods().get(0); return new StatementTarget(startMethod, true); } //////////////////////////////////////////////////////////////////////////// // // NodeTarget // //////////////////////////////////////////////////////////////////////////// /** * @return the {@link NodeTarget} directly before creation of given {@link JavaInfo}. */ public static NodeTarget getNodeTarget_beforeCreation(JavaInfo javaInfo) throws Exception { return getNodeTarget_relativeCreation(javaInfo, true); } /** * @return the {@link NodeTarget} directly after creation of given {@link JavaInfo}. */ public static NodeTarget getNodeTarget_afterCreation(JavaInfo javaInfo) throws Exception { return getNodeTarget_relativeCreation(javaInfo, false); } /** * @return the {@link NodeTarget} relative creation of given {@link JavaInfo}. */ private static NodeTarget getNodeTarget_relativeCreation(JavaInfo javaInfo, boolean before) throws Exception { CreationSupport creationSupport = javaInfo.getCreationSupport(); // Special support for control of wrapper/viewer. { if (creationSupport instanceof WrapperMethodControlCreationSupport) { StatementTarget statementTarget = javaInfo.getVariableSupport().getStatementTarget(); if (statementTarget != null) { return new NodeTarget(statementTarget); } } } // general case ASTNode node = creationSupport.getNode(); { Statement statement = AstNodeUtils.getEnclosingStatement(node); if (statement != null) { return new NodeTarget(new StatementTarget(statement, before)); } } { BodyDeclaration bodyDeclaration = AstNodeUtils.getEnclosingNode(node, BodyDeclaration.class); Assert.isNotNull(bodyDeclaration, "No Statement and no BodyDeclaration for %s %s in %s", javaInfo, node, node.getRoot()); return new NodeTarget(new BodyDeclarationTarget(bodyDeclaration, before)); } } //////////////////////////////////////////////////////////////////////////// // // Rendering // //////////////////////////////////////////////////////////////////////////// /** * Schedules invocation of {@link IJavaInfoRendering#render()} when execution flow leaves * constructor. * <p> * This method should specify that this rendering is performed for some active {@link Statement} * in {@link ExecutionFlowDescription}. We use constructor body as such {@link Statement}. * * @see IJavaInfoRendering IJavaInfoRendering for more information. */ public static void scheduleSpecialRendering(JavaInfo javaInfo) { if (javaInfo instanceof IJavaInfoRendering) { scheduleSpecialRendering(javaInfo, (IJavaInfoRendering) javaInfo); } } /** * Schedules invocation of {@link IJavaInfoRendering#render()} when execution flow leaves * constructor. * <p> * This method should specify that this rendering is performed for some active {@link Statement} * in {@link ExecutionFlowDescription}. We use constructor body as such {@link Statement}. * * @see IJavaInfoRendering IJavaInfoRendering for more information. */ public static void scheduleSpecialRendering(JavaInfo javaInfo, final IJavaInfoRendering rendering) { if (!(javaInfo.getCreationSupport() instanceof ThisCreationSupport)) { return; } // prepare JavaInfo elements final EditorState editorState = getState(javaInfo); final MethodDeclaration constructor = (MethodDeclaration) javaInfo.getCreationSupport().getNode(); final Statement statement = constructor.getBody(); // add rendering listener javaInfo.addBroadcastListener(new EvaluationEventListener() { @Override public void leaveFrame(ASTNode node) throws Exception { if (node == constructor) { ExecutionFlowDescription flowDescription = editorState.getFlowDescription(); flowDescription.enterStatement(statement); try { rendering.render(); } finally { flowDescription.leaveStatement(statement); } } } }); } //////////////////////////////////////////////////////////////////////////// // // Wrappers // //////////////////////////////////////////////////////////////////////////// /** * Sometimes real component is created using some wrapper, for example as {@link Viewer} creates * {@link Control}. We should drop {@link Viewer} in code, but layout policies should see that we * drop {@link Control}. So we should "extract" {@link Control} {@link JavaInfo} from * {@link Viewer} {@link JavaInfo}. * * @return the wrapped {@link JavaInfo} or original {@link JavaInfo} if there are no wrapping. */ public static JavaInfo getWrapped(JavaInfo original) throws Exception { if (original instanceof IWrapperInfo) { IWrapperInfo wrapperInfo = (IWrapperInfo) original; return wrapperInfo.getWrapper().getWrappedInfo(); } return original; } //////////////////////////////////////////////////////////////////////////// // // Deleting // //////////////////////////////////////////////////////////////////////////// /** * Deletes children and related nodes of given {@link JavaInfo}.<br> * * @param removeFromParent * is <code>true</code>, also removes from from parent. Usually it is <code>true</code>, * but exposed components can not be really deleted, because they belong to its host, so * only children and related nodes can be removed. */ public static void deleteJavaInfo(JavaInfo javaInfo, boolean removeFromParent) throws Exception { // delete children List<ObjectInfo> children = ImmutableList.copyOf(javaInfo.getChildren()); for (ObjectInfo child : children) { // There are cases when children of some parent are "linked", so one deletes other on delete. // So, we should check, may be child is already deleted. if (!child.isDeleted()) { child.delete(); } } // remove statements of related nodes for (ASTNode node : javaInfo.getRelatedNodes()) { // we can have several related nodes in same Statement, so may be already removed if (AstNodeUtils.isDanglingNode(node)) { continue; } // may be creation node, when we don't want to remove it if (!removeFromParent && node == javaInfo.getCreationSupport().getNode()) { continue; } // do remove Statement statement = AstNodeUtils.getEnclosingStatement(node); if (statement != null) { javaInfo.getEditor().removeStatement(statement); } } // remove from parent if (removeFromParent) { javaInfo.getParent().removeChild(javaInfo); } } //////////////////////////////////////////////////////////////////////////// // // Reparse on dependency change // //////////////////////////////////////////////////////////////////////////// private static final String DEPENDENCY_KEY = "JavaInfo.dependencies"; /** * @return <code>true</code> if dependency information for hierarchy was prepared. */ public static boolean hasDependencyInformation(JavaInfo javaInfo) throws Exception { return javaInfo.getEditor().getGlobalValue(DEPENDENCY_KEY) != null; } /** * @return <code>true</code> if one of the referenced types was changed. */ @SuppressWarnings("unchecked") public static boolean isDependencyChanged(JavaInfo javaInfo) throws Exception { Map<IResource, Long> dependencies = (Map<IResource, Long>) javaInfo.getEditor() .getGlobalValue(DEPENDENCY_KEY); if (dependencies != null) { for (Map.Entry<IResource, Long> entry : dependencies.entrySet()) { if (entry.getKey().getModificationStamp() != entry.getValue()) { return true; } } } return false; } /** * Remembers dependency information for given {@link JavaInfo}, i.e. time stamps for referenced * types. */ public static void rememberDependency(JavaInfo javaInfo) throws Exception { AstEditor editor = javaInfo.getEditor(); // prepare dependencies Map<IResource, Long> dependencies = Maps.newHashMap(); addDependencies(dependencies, Sets.<String>newTreeSet(), editor.getModelUnit(), 0); // don't use this compilation unit resource dependencies.remove(editor.getModelUnit().getResource()); // remember dependencies editor.putGlobalValue(DEPENDENCY_KEY, dependencies); } /** * Adds dependencies for given {@link ICompilationUnit}. */ private static void addDependencies(final Map<IResource, Long> dependencies, final Set<String> checkedTypes, final ICompilationUnit modelUnit, final int level) throws Exception { if (level < 5 && dependencies.size() < 100) { final IJavaProject javaProject = modelUnit.getJavaProject(); // add current resource { IResource resource = modelUnit.getResource(); dependencies.put(resource, resource.getModificationStamp()); } // add references CompilationUnit astUnit = CodeUtils.parseCompilationUnit(modelUnit); astUnit.accept(new ASTVisitor() { @Override public void endVisit(QualifiedName node) { addNewType(node.resolveTypeBinding()); } @Override public void endVisit(SimpleName node) { addNewType(node.resolveTypeBinding()); } private void addNewType(final ITypeBinding binding) { if (binding == null) { return; } ExecutionUtils.runIgnore(new RunnableEx() { public void run() throws Exception { String typeName = AstNodeUtils.getFullyQualifiedName(binding, false); if (typeName.indexOf('.') != -1 && !checkedTypes.contains(typeName)) { checkedTypes.add(typeName); IType type = javaProject.findType(typeName); if (type != null && !type.isBinary()) { addDependencies(dependencies, checkedTypes, type.getCompilationUnit(), level + 1); } } } }); } }); } } //////////////////////////////////////////////////////////////////////////// // // EditorState // //////////////////////////////////////////////////////////////////////////// /** * @return the {@link EditorState} instance for given {@link JavaInfo}. */ public static EditorState getState(JavaInfo javaInfo) { return EditorState.get(javaInfo.getEditor()); } /** * @return the {@link ClassLoader} for given {@link JavaInfo}. */ public static ClassLoader getClassLoader(JavaInfo javaInfo) { return getState(javaInfo).getEditorLoader(); } }