Java tutorial
/******************************************************************************* * Copyright 2011 Google Inc. All Rights Reserved. * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.google.gdt.eclipse.designer.uibinder.model.util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gdt.eclipse.designer.uibinder.Activator; import com.google.gdt.eclipse.designer.uibinder.model.widgets.WidgetInfo; import com.google.gdt.eclipse.designer.uibinder.parser.UiBinderContext; import org.eclipse.wb.core.model.ObjectInfo; import org.eclipse.wb.core.model.broadcast.ObjectInfoChildrenTree; import org.eclipse.wb.internal.core.model.presentation.DefaultObjectPresentation; import org.eclipse.wb.internal.core.model.presentation.IObjectPresentation; import org.eclipse.wb.internal.core.model.property.ComplexProperty; import org.eclipse.wb.internal.core.model.property.Property; import org.eclipse.wb.internal.core.model.property.category.PropertyCategory; import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor; import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils; import org.eclipse.wb.internal.core.utils.reflect.ClassMap; import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils; import org.eclipse.wb.internal.core.utils.xml.DocumentElement; import org.eclipse.wb.internal.core.xml.model.XmlObjectInfo; import org.eclipse.wb.internal.core.xml.model.association.Association; import org.eclipse.wb.internal.core.xml.model.association.DirectAssociation; import org.eclipse.wb.internal.core.xml.model.broadcast.XmlObjectAddProperties; import org.eclipse.wb.internal.core.xml.model.description.DescriptionPropertiesHelper; import org.eclipse.wb.internal.core.xml.model.description.GenericPropertyDescription; import org.eclipse.wb.internal.core.xml.model.property.GenericPropertyImpl; import org.eclipse.wb.internal.core.xml.model.property.accessor.ExpressionAccessor; import org.eclipse.wb.internal.core.xml.model.property.converter.ExpressionConverter; import org.eclipse.wb.internal.core.xml.model.utils.ElementTarget; import org.eclipse.wb.internal.core.xml.model.utils.XmlObjectUtils; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.swt.graphics.Image; import org.apache.commons.lang.StringUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; /** * Support for @UiChild positions. * * @author scheglov_ke * @coverage GWT.UiBinder.model */ public final class UiChildSupport { private static final String UI_CHILD = "com.google.gwt.uibinder.client.UiChild"; private final UiBinderContext m_context; private final ClassMap<List<Description>> m_descriptions = ClassMap.create(); private final Map<WidgetInfo, Map<String, Position>> m_positions = new MapMaker().weakKeys().makeMap(); private final Map<XmlObjectInfo, Property> m_properties = new MapMaker().weakKeys().makeMap(); //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// public UiChildSupport(UiBinderContext context) { m_context = context; m_context.getBroadcastSupport().addListener(null, new ObjectInfoChildrenTree() { public void invoke(ObjectInfo parent, List<ObjectInfo> children) throws Exception { processTreeChildren(parent, children); } }); m_context.getBroadcastSupport().addListener(null, new XmlObjectAddProperties() { public void invoke(XmlObjectInfo object, List<Property> properties) throws Exception { Property property = getProperty(object); if (property != null) { properties.add(property); } } }); } //////////////////////////////////////////////////////////////////////////// // // Implementation // //////////////////////////////////////////////////////////////////////////// private void processTreeChildren(ObjectInfo parent, List<ObjectInfo> children) throws Exception { // exclude @UiChild widgets from parent if (parent instanceof WidgetInfo) { WidgetInfo widget = (WidgetInfo) parent; // may be support for @UiChild is disabled if (XmlObjectUtils.hasTrueParameter(widget, "UiChild.disabled")) { return; } // include Position objects { Map<String, Position> tagToPosition = getPositions(widget); for (Position position : tagToPosition.values()) { children.add(position); position.setParent(parent); } } // exclude widgets for (WidgetInfo child : widget.getChildren(WidgetInfo.class)) { Position position = getPosition(widget, child); if (position != null) { children.remove(child); } } } } /** * @return the {@link Position} to which given child is bound, may be <code>null</code>. */ private Position getPosition(WidgetInfo widget, WidgetInfo child) throws Exception { Map<String, Position> tagToPosition = getPositions(widget); DocumentElement childElementParent = child.getElement().getParent(); String tag = childElementParent.getTagLocal(); return tagToPosition.get(tag); } /** * @return the {@link Position} to which given object is bound as {@link WidgetInfo}, may be * <code>null</code> if not {@link WidgetInfo} or not bound. */ private Position getPosition(XmlObjectInfo object) throws Exception { if (object instanceof WidgetInfo && object.getParent() instanceof WidgetInfo) { return getPosition((WidgetInfo) object.getParent(), (WidgetInfo) object); } return null; } /** * Prepare information for {@link WidgetInfo}. */ private Map<String, Position> getPositions(WidgetInfo widget) throws Exception { Map<String, Position> tagToPosition = m_positions.get(widget); if (tagToPosition == null) { // prepare tags to hide Set<String> hideTags = Sets.newHashSet(); { String hideString = XmlObjectUtils.getParameter(widget, "UiChild.hide"); if (hideString != null) { String[] hideSplit = StringUtils.split(hideString); hideTags = ImmutableSet.copyOf(hideSplit); } } // remember positions for Widget tagToPosition = Maps.newLinkedHashMap(); m_positions.put(widget, tagToPosition); // fill positions for (Description description : getSortedDescriptions(widget)) { String tag = description.getTag(); if (!hideTags.contains(tag)) { Position position = new Position(widget, description); tagToPosition.put(tag, position); } } } return tagToPosition; } //////////////////////////////////////////////////////////////////////////// // // "UiChild" property // //////////////////////////////////////////////////////////////////////////// /** * @return the complex {@link Property} for parent {@link Position}. */ private Property getProperty(XmlObjectInfo object) throws Exception { Property property = m_properties.get(object); if (property == null) { property = createProperty(object); if (property != null) { m_properties.put(object, property); } } return property; } /** * Creates {@link Property} for {@link #getProperty(XmlObjectInfo)}, may be <code>null</code>. */ private Property createProperty(XmlObjectInfo object) throws Exception { // prepare @UiChild method Method method; { Position position = getPosition(object); if (position == null) { return null; } method = position.getMethod(); } // if no other parameters than Widget, then no property if (method.getParameterTypes().length < 2) { return null; } // prepare sub-properties List<Property> subPropertiesList = Lists.newArrayList(); String[] parameterNames = getMethodParameterNames(method); Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 1; i < parameterTypes.length; i++) { Property property = createProperty(object, parameterNames[i], parameterTypes[i]); if (property != null) { subPropertiesList.add(property); } } // if no sub-properties, then no property if (subPropertiesList.isEmpty()) { return null; } // prepare @UiChild complex property ComplexProperty methodProperty = new ComplexProperty("UiChild", "(Properties)"); methodProperty.setCategory(PropertyCategory.system(3)); methodProperty.setModified(true); methodProperty.setTooltip("Properties for @UiChild arguments."); // set sub-properties methodProperty.setProperties(subPropertiesList); return methodProperty; } /** * @return the {@link Property} for single @UiChild parameter. */ private static Property createProperty(XmlObjectInfo object, String name, Class<?> type) throws Exception { ExpressionConverter converter = DescriptionPropertiesHelper.getConverterForType(type); PropertyEditor editor = DescriptionPropertiesHelper.getEditorForType(type); if (converter == null || editor == null) { return null; } ExpressionAccessor accessor = new ExpressionAccessor(name) { @Override protected DocumentElement getExistingElement(XmlObjectInfo object) { return object.getElement().getParent(); } @Override protected DocumentElement getElement(XmlObjectInfo object) { return getExistingElement(object); } @Override public Object getValue(XmlObjectInfo object) throws Exception { UiBinderContext context = (UiBinderContext) object.getContext(); return context.getAttributeValue(getElement(object), m_attribute); } }; GenericPropertyDescription description = new GenericPropertyDescription(name, name, type, accessor); description.setConverter(converter); description.setEditor(editor); return new GenericPropertyImpl(object, description); } /** * @return the names of {@link Method} parameters, not <code>null</code>. */ private String[] getMethodParameterNames(Method reflectionMethod) throws Exception { IJavaProject javaProject = m_context.getJavaProject(); IMethod javaMethod = CodeUtils.findMethod(javaProject, reflectionMethod.getDeclaringClass().getName(), ReflectionUtils.getMethodSignature(reflectionMethod)); return javaMethod.getParameterNames(); } //////////////////////////////////////////////////////////////////////////// // // Description // //////////////////////////////////////////////////////////////////////////// /** * @return the sorted {@link Description}s for methods of given {@link WidgetInfo}. */ private List<Description> getSortedDescriptions(WidgetInfo widget) throws Exception { // prepare order of tags final List<String> tagOrder; { String hideString = XmlObjectUtils.getParameter(widget, "UiChild.order"); if (hideString != null) { String[] hideSplit = StringUtils.split(hideString); tagOrder = ImmutableList.copyOf(hideSplit); } else { tagOrder = ImmutableList.of(); } } // sort descriptions List<Description> descriptions = getDescriptions(widget); Collections.sort(descriptions, new Comparator<Description>() { public int compare(Description o1, Description o2) { String tag1 = o1.getTag(); String tag2 = o2.getTag(); int index1 = tagOrder.indexOf(tag1); int index2 = tagOrder.indexOf(tag2); if (index1 != -1 && index2 != -1) { return index1 - index2; } if (index1 != -1) { return -1; } if (index2 != -1) { return 1; } return tag1.compareTo(tag2); } }); return descriptions; } /** * @return the {@link Description}s for methods of given {@link WidgetInfo}. */ private List<Description> getDescriptions(WidgetInfo widget) throws Exception { Class<?> componentClass = widget.getDescription().getComponentClass(); List<Description> descriptions = m_descriptions.get(componentClass); if (descriptions == null) { descriptions = Lists.newArrayList(); for (Method method : componentClass.getMethods()) { Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { if (ReflectionUtils.isSuccessorOf(annotation, UI_CHILD)) { descriptions.add(new Description(method, annotation)); } } } m_descriptions.put(componentClass, descriptions); } return descriptions; } /** * Description for single @UiChild annotated method. */ private static final class Description { private final Method m_method; private final String m_tag; private final int m_limit; private final Class<?> m_widgetClass; //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// public Description(Method method, Annotation annotation) throws Exception { m_method = method; m_tag = getTag(method, annotation); m_limit = getLimit(annotation); m_widgetClass = method.getParameterTypes()[0]; } //////////////////////////////////////////////////////////////////////////// // // Private access // //////////////////////////////////////////////////////////////////////////// private static String getTag(Method method, Annotation annotation) throws Exception { String tag = (String) ReflectionUtils.invokeMethod(annotation, "tagname()"); if (StringUtils.isEmpty(tag)) { tag = StringUtils.removeStart(method.getName(), "add").toLowerCase(); } return tag; } private static Integer getLimit(Annotation annotation) throws Exception { int limit = (Integer) ReflectionUtils.invokeMethod(annotation, "limit()"); if (limit == -1) { limit = Integer.MAX_VALUE; } return limit; } //////////////////////////////////////////////////////////////////////////// // // Access // //////////////////////////////////////////////////////////////////////////// public Method getMethod() { return m_method; } public String getTag() { return m_tag; } public int getLimit() { return m_limit; } public Class<?> getWidgetClass() { return m_widgetClass; } } //////////////////////////////////////////////////////////////////////////// // // Position // //////////////////////////////////////////////////////////////////////////// /** * Model for @UiChild element. */ public final class Position extends ObjectInfo { private final WidgetInfo m_widget; private final Description m_description; //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// public Position(WidgetInfo widget, Description description) throws Exception { m_widget = widget; m_description = description; } //////////////////////////////////////////////////////////////////////////// // // Access // //////////////////////////////////////////////////////////////////////////// public Method getMethod() { return m_description.getMethod(); } public String getTag() { return m_description.getTag(); } public Class<?> getWidgetClass() { return m_description.getWidgetClass(); } public boolean canAddChild() { return getWidgets().size() < m_description.getLimit(); } private List<WidgetInfo> getWidgets() { List<WidgetInfo> widgets = Lists.newArrayList(); for (WidgetInfo child : m_widget.getChildren(WidgetInfo.class)) { DocumentElement childElementParent = child.getElement().getParent(); String tag = childElementParent.getTagLocal(); String positionTag = m_description.getTag(); if (tag.equals(positionTag)) { widgets.add(child); } } return widgets; } //////////////////////////////////////////////////////////////////////////// // // Presentation // //////////////////////////////////////////////////////////////////////////// @Override public IObjectPresentation getPresentation() { return new DefaultObjectPresentation(this) { public String getText() throws Exception { return m_description.getTag(); } @Override public Image getIcon() throws Exception { return Activator.getImage("info/UiChild.png"); } @Override public List<ObjectInfo> getChildrenTree() throws Exception { List<WidgetInfo> widgets = getWidgets(); return Lists.<ObjectInfo>newArrayList(widgets); } }; } //////////////////////////////////////////////////////////////////////////// // // Commands // //////////////////////////////////////////////////////////////////////////// public void command_CREATE(WidgetInfo widget, WidgetInfo reference) throws Exception { XmlObjectUtils.add(widget, getAssociation(), m_widget, reference); } public void command_MOVE(WidgetInfo widget, WidgetInfo reference) throws Exception { XmlObjectUtils.move(widget, getAssociation(), m_widget, reference); } private Association getAssociation() { return new DirectAssociation() { @Override public void add(XmlObjectInfo object, ElementTarget target) throws Exception { target = prepareTarget(target); super.add(object, target); } @Override //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "BC_UNCONFIRMED_CAST") public void move(XmlObjectInfo object, ElementTarget target, XmlObjectInfo oldParent, XmlObjectInfo newParent) throws Exception { WidgetInfo widget = (WidgetInfo) object; // reorder, use same "position" element { Position position = getPosition(m_widget, widget); if (position == Position.this) { DocumentElement targetElement = target.getElement(); int targetIndex = target.getIndex(); targetElement.moveChild(object.getElement().getParent(), targetIndex); return; } } // create new "position" element target = prepareTarget(target); super.move(object, target, oldParent, newParent); } private ElementTarget prepareTarget(ElementTarget target) { // prepare "position" element String tag = m_widget.getElement().getTagNS() + m_description.getTag(); DocumentElement positionElement = new DocumentElement(tag); // add "position" element DocumentElement targetElement = target.getElement(); int targetIndex = target.getIndex(); targetElement.addChild(positionElement, targetIndex); // prepare new target return new ElementTarget(positionElement, 0); } }; } } }