diff options
author | John Ahlroos <john@vaadin.com> | 2013-03-01 15:00:47 +0200 |
---|---|---|
committer | John Ahlroos <john@vaadin.com> | 2013-03-01 15:03:05 +0200 |
commit | c965ff2475f4047ae5efa2b2917089f8b97e95d9 (patch) | |
tree | 76e29313c468e791dba2dbae30aefca105b48d81 | |
parent | eb65236d0eee3cbeebe5a2d5fa5a2f3744ebaeff (diff) | |
parent | 7f9e51e0e31c5c0a699c7ca486aaf993d5aec347 (diff) | |
download | vaadin-framework-c965ff2475f4047ae5efa2b2917089f8b97e95d9.tar.gz vaadin-framework-c965ff2475f4047ae5efa2b2917089f8b97e95d9.zip |
Merge remote-tracking branch 'origin/7.0'
Change-Id: Ifdc095169c31731cb013727f14d92839ecc775db
48 files changed, 915 insertions, 366 deletions
diff --git a/buildhelpers/src/com/vaadin/buildhelpers/FetchReleaseNotesTickets.java b/buildhelpers/src/com/vaadin/buildhelpers/FetchReleaseNotesTickets.java index 76952bb329..588fe1aba1 100644 --- a/buildhelpers/src/com/vaadin/buildhelpers/FetchReleaseNotesTickets.java +++ b/buildhelpers/src/com/vaadin/buildhelpers/FetchReleaseNotesTickets.java @@ -1,3 +1,18 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.vaadin.buildhelpers; import java.io.IOException; diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java index 90877c47c8..34024f1616 100644 --- a/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java @@ -239,7 +239,7 @@ public class ClassPathExplorer { classpath = classpath.substring(0, classpath.length() - 1); } - getLogger().fine("Classpath: " + classpath); + getLogger().log(Level.FINE, "Classpath: {0}", classpath); String[] split = classpath.split(pathSep); for (int i = 0; i < split.length; i++) { diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index de034a65a6..1a637e3161 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -16,15 +16,13 @@ package com.vaadin.client; -import java.util.ArrayList; -import java.util.Collection; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -113,8 +111,15 @@ public class ApplicationConnection { * Helper used to return two values when updating the connector hierarchy. */ private static class ConnectorHierarchyUpdateResult { - private List<ConnectorHierarchyChangeEvent> events = new LinkedList<ConnectorHierarchyChangeEvent>(); - private List<ServerConnector> parentChanged = new LinkedList<ServerConnector>(); + /** + * Needed at a later point when the created events are fired + */ + private JsArrayObject<ConnectorHierarchyChangeEvent> events = JavaScriptObject + .createArray().cast(); + /** + * Needed to know where captions might need to get updated + */ + private FastStringSet parentChangedIds = FastStringSet.create(); } public static final String MODIFIED_CLASSNAME = "v-modified"; @@ -153,9 +158,6 @@ public class ApplicationConnection { // will hold the UIDL security key (for XSS protection) once received private String uidlSecurityKey = "init"; - private static final FastStringMap<FastStringSet> allStateFieldsCache = FastStringMap - .create(); - private final HashMap<String, String> resourcesMap = new HashMap<String, String>(); /** @@ -217,8 +219,6 @@ public class ApplicationConnection { /** redirectTimer scheduling interval in seconds */ private int sessionExpirationInterval; - private ArrayList<Widget> componentCaptionSizeChanges = new ArrayList<Widget>(); - private Date requestStartTime; private boolean validatingLayouts = false; @@ -1424,16 +1424,14 @@ public class ApplicationConnection { redirectTimer.schedule(1000 * sessionExpirationInterval); } - componentCaptionSizeChanges.clear(); - double processUidlStart = Duration.currentTimeMillis(); // Ensure that all connectors that we are about to update exist - Set<ServerConnector> createdConnectors = createConnectorsIfNeeded(json); + JsArrayString createdConnectorIds = createConnectorsIfNeeded(json); // Update states, do not fire events - Collection<StateChangeEvent> pendingStateChangeEvents = updateConnectorState( - json, createdConnectors); + JsArrayObject<StateChangeEvent> pendingStateChangeEvents = updateConnectorState( + json, createdConnectorIds); // Update hierarchy, do not fire events ConnectorHierarchyUpdateResult connectorHierarchyUpdateResult = updateConnectorHierarchy(json); @@ -1442,7 +1440,7 @@ public class ApplicationConnection { sendHierarchyChangeEvents(connectorHierarchyUpdateResult.events); updateCaptions(pendingStateChangeEvents, - connectorHierarchyUpdateResult.parentChanged); + connectorHierarchyUpdateResult.parentChangedIds); delegateToWidget(pendingStateChangeEvents); @@ -1566,27 +1564,35 @@ public class ApplicationConnection { } private void updateCaptions( - Collection<StateChangeEvent> pendingStateChangeEvents, - Collection<ServerConnector> parentChanged) { + JsArrayObject<StateChangeEvent> pendingStateChangeEvents, + FastStringSet parentChangedIds) { Profiler.enter("updateCaptions"); /* * Find all components that might need a caption update based on * pending state and hierarchy changes */ - HashSet<ServerConnector> needsCaptionUpdate = new HashSet<ServerConnector>( - parentChanged); + FastStringSet needsCaptionUpdate = FastStringSet.create(); + needsCaptionUpdate.addAll(parentChangedIds); // Find components with potentially changed caption state - for (StateChangeEvent event : pendingStateChangeEvents) { + int size = pendingStateChangeEvents.size(); + for (int i = 0; i < size; i++) { + StateChangeEvent event = pendingStateChangeEvents.get(i); if (VCaption.mightChange(event)) { ServerConnector connector = event.getConnector(); - needsCaptionUpdate.add(connector); + needsCaptionUpdate.add(connector.getConnectorId()); } } + ConnectorMap connectorMap = getConnectorMap(); + // Update captions for all suitable candidates - for (ServerConnector child : needsCaptionUpdate) { + JsArrayString dump = needsCaptionUpdate.dump(); + int needsUpdateLength = dump.length(); + for (int i = 0; i < needsUpdateLength; i++) { + String childId = dump.get(i); + ServerConnector child = connectorMap.getConnector(childId); if (child instanceof ComponentConnector && ((ComponentConnector) child) .delegateCaptionHandling()) { @@ -1604,29 +1610,45 @@ public class ApplicationConnection { } private void delegateToWidget( - Collection<StateChangeEvent> pendingStateChangeEvents) { + JsArrayObject<StateChangeEvent> pendingStateChangeEvents) { Profiler.enter("@DelegateToWidget"); VConsole.log(" * Running @DelegateToWidget"); - for (StateChangeEvent sce : pendingStateChangeEvents) { + // Keep track of types that have no @DelegateToWidget in their + // state to optimize performance + FastStringSet noOpTypes = FastStringSet.create(); + + int size = pendingStateChangeEvents.size(); + for (int eventIndex = 0; eventIndex < size; eventIndex++) { + StateChangeEvent sce = pendingStateChangeEvents + .get(eventIndex); ServerConnector connector = sce.getConnector(); if (connector instanceof ComponentConnector) { + String className = connector.getClass().getName(); + if (noOpTypes.contains(className)) { + continue; + } ComponentConnector component = (ComponentConnector) connector; Type stateType = AbstractConnector .getStateType(component); + JsArrayString delegateToWidgetProperties = stateType + .getDelegateToWidgetProperties(); + if (delegateToWidgetProperties == null) { + noOpTypes.add(className); + continue; + } - FastStringSet changedProperties = sce - .getChangedPropertiesFastSet(); - JsArrayString dump = changedProperties.dump(); - for (int i = 0; i < dump.length(); i++) { - String propertyName = dump.get(i); - Property property = stateType - .getProperty(propertyName); - String method = property - .getDelegateToWidgetMethodName(); - if (method != null) { + int length = delegateToWidgetProperties.length(); + for (int i = 0; i < length; i++) { + String propertyName = delegateToWidgetProperties + .get(i); + if (sce.hasPropertyChanged(propertyName)) { + Property property = stateType + .getProperty(propertyName); + String method = property + .getDelegateToWidgetMethodName(); Profiler.enter("doDelegateToWidget"); doDelegateToWidget(component, property, method); Profiler.leave("doDelegateToWidget"); @@ -1673,11 +1695,13 @@ public class ApplicationConnection { * The events to send */ private void sendStateChangeEvents( - Collection<StateChangeEvent> pendingStateChangeEvents) { + JsArrayObject<StateChangeEvent> pendingStateChangeEvents) { Profiler.enter("sendStateChangeEvents"); VConsole.log(" * Sending state change events"); - for (StateChangeEvent sce : pendingStateChangeEvents) { + int size = pendingStateChangeEvents.size(); + for (int i = 0; i < size; i++) { + StateChangeEvent sce = pendingStateChangeEvents.get(i); try { sce.getConnector().fireEvent(sce); } catch (final Throwable e) { @@ -1723,29 +1747,29 @@ public class ApplicationConnection { Profiler.leave("unregisterRemovedConnectors"); } - private Set<ServerConnector> createConnectorsIfNeeded(ValueMap json) { + private JsArrayString createConnectorsIfNeeded(ValueMap json) { VConsole.log(" * Creating connectors (if needed)"); + JsArrayString createdConnectors = JavaScriptObject + .createArray().cast(); if (!json.containsKey("types")) { - return Collections.emptySet(); + return createdConnectors; } Profiler.enter("Creating connectors"); - Set<ServerConnector> createdConnectors = new HashSet<ServerConnector>(); - ValueMap types = json.getValueMap("types"); JsArrayString keyArray = types.getKeyArray(); for (int i = 0; i < keyArray.length(); i++) { try { String connectorId = keyArray.get(i); - int connectorType = Integer.parseInt(types - .getString((connectorId))); ServerConnector connector = connectorMap .getConnector(connectorId); if (connector != null) { continue; } + int connectorType = Integer.parseInt(types + .getString(connectorId)); Class<? extends ServerConnector> connectorClass = configuration .getConnectorClassByEncodedTag(connectorType); @@ -1757,7 +1781,7 @@ public class ApplicationConnection { connector = getConnector(connectorId, connectorType); Profiler.leave("ApplicationConnection.getConnector"); - createdConnectors.add(connector); + createdConnectors.push(connectorId); } else { // First UIConnector update. Before this the // UIConnector has been created but not @@ -1767,7 +1791,7 @@ public class ApplicationConnection { uIConnector); uIConnector.doInit(connectorId, ApplicationConnection.this); - createdConnectors.add(uIConnector); + createdConnectors.push(connectorId); } } catch (final Throwable e) { VConsole.error(e); @@ -1829,14 +1853,16 @@ public class ApplicationConnection { } private void sendHierarchyChangeEvents( - Collection<ConnectorHierarchyChangeEvent> pendingHierarchyChangeEvents) { - if (pendingHierarchyChangeEvents.isEmpty()) { + JsArrayObject<ConnectorHierarchyChangeEvent> events) { + int eventCount = events.size(); + if (eventCount == 0) { return; } Profiler.enter("sendHierarchyChangeEvents"); VConsole.log(" * Sending hierarchy change events"); - for (ConnectorHierarchyChangeEvent event : pendingHierarchyChangeEvents) { + for (int i = 0; i < eventCount; i++) { + ConnectorHierarchyChangeEvent event = events.get(i); try { logHierarchyChange(event); event.getConnector().fireEvent(event); @@ -1871,9 +1897,10 @@ public class ApplicationConnection { VConsole.log(newChildren); } - private Collection<StateChangeEvent> updateConnectorState( - ValueMap json, Set<ServerConnector> newConnectors) { - ArrayList<StateChangeEvent> events = new ArrayList<StateChangeEvent>(); + private JsArrayObject<StateChangeEvent> updateConnectorState( + ValueMap json, JsArrayString createdConnectorIds) { + JsArrayObject<StateChangeEvent> events = JavaScriptObject + .createArray().cast(); VConsole.log(" * Updating connector states"); if (!json.containsKey("state")) { return events; @@ -1881,8 +1908,8 @@ public class ApplicationConnection { Profiler.enter("updateConnectorState"); - HashSet<ServerConnector> remainingNewConnectors = new HashSet<ServerConnector>( - newConnectors); + FastStringSet remainingNewConnectors = FastStringSet.create(); + remainingNewConnectors.addAll(createdConnectorIds); // set states for all paintables mentioned in "state" ValueMap states = json.getValueMap("state"); @@ -1923,22 +1950,15 @@ public class ApplicationConnection { } Profiler.enter("updateConnectorState create event"); - FastStringSet changedProperties = FastStringSet - .create(); - addJsonFields(stateJson, changedProperties, ""); - - if (newConnectors.contains(connector)) { - remainingNewConnectors.remove(connector); - // Fire events for properties using the default - // value for newly created connectors - FastStringSet allStateFields = getAllStateFields(AbstractConnector - .getStateType(connector)); - changedProperties.addAll(allStateFields); + + boolean isNewConnector = remainingNewConnectors + .contains(connectorId); + if (isNewConnector) { + remainingNewConnectors.remove(connectorId); } StateChangeEvent event = new StateChangeEvent( - connector, changedProperties); - + connector, stateJson, isNewConnector); events.add(event); Profiler.leave("updateConnectorState create event"); @@ -1952,12 +1972,15 @@ public class ApplicationConnection { Profiler.enter("updateConnectorState newWithoutState"); // Fire events for properties using the default value for newly // created connectors even if there were no state changes - for (ServerConnector connector : remainingNewConnectors) { - FastStringSet changedProperties = getAllStateFields(AbstractConnector - .getStateType(connector)); + JsArrayString dump = remainingNewConnectors.dump(); + int length = dump.length(); + for (int i = 0; i < length; i++) { + String connectorId = dump.get(i); + ServerConnector connector = connectorMap + .getConnector(connectorId); StateChangeEvent event = new StateChangeEvent(connector, - changedProperties); + new JSONObject(), true); events.add(event); @@ -1969,80 +1992,6 @@ public class ApplicationConnection { return events; } - private FastStringSet getAllStateFields(Type type) { - FastStringSet fields; - fields = allStateFieldsCache.get(type.getBaseTypeName()); - if (fields == null) { - Profiler.enter("getAllStateFields create"); - fields = FastStringSet.create(); - addAllStateFields(type, fields, ""); - allStateFieldsCache.put(type.getBaseTypeName(), fields); - Profiler.leave("getAllStateFields create"); - } - return fields; - } - - /** - * Recursively adds the names of all properties in the provided - * state type. - * - * @param type - * the type to process - * @param foundProperties - * a set of all currently added properties - * @param context - * the base name of the current object - */ - private void addAllStateFields(Type type, - FastStringSet foundProperties, String context) { - try { - JsArrayObject<Property> properties = type - .getPropertiesAsArray(); - int size = properties.size(); - for (int i = 0; i < size; i++) { - Property property = properties.get(i); - String propertyName = context + property.getName(); - foundProperties.add(propertyName); - - Type propertyType = property.getType(); - if (propertyType.hasProperties()) { - addAllStateFields(propertyType, foundProperties, - propertyName + "."); - } - } - } catch (NoDataException e) { - throw new IllegalStateException( - "No property info for " - + type - + ". Did you remember to compile the right widgetset?", - e); - } - } - - /** - * Recursively adds the names of all fields in all objects in the - * provided json object. - * - * @param json - * the json object to process - * @param fields - * a set of all currently added fields - * @param context - * the base name of the current object - */ - private void addJsonFields(JSONObject json, FastStringSet fields, - String context) { - for (String key : json.keySet()) { - String fieldName = context + key; - fields.add(fieldName); - - JSONObject object = json.get(key).isObject(); - if (object != null) { - addJsonFields(object, fields, fieldName + "."); - } - } - } - /** * Updates the connector hierarchy and returns a list of events that * should be fired after update of the hierarchy and the state is @@ -2106,7 +2055,7 @@ public class ApplicationConnection { } if (childConnector.getParent() != parentConnector) { childConnector.setParent(parentConnector); - result.parentChanged.add(childConnector); + result.parentChangedIds.add(childConnectorId); // Not detached even if previously removed from // parent maybeDetached.remove(childConnectorId); @@ -2191,7 +2140,7 @@ public class ApplicationConnection { } private void recursivelyDetach(ServerConnector connector, - List<ConnectorHierarchyChangeEvent> events) { + JsArrayObject<ConnectorHierarchyChangeEvent> events) { /* * Reset state in an attempt to keep it consistent with the @@ -3089,9 +3038,11 @@ public class ApplicationConnection { * * @param component * the Paintable whose caption has changed + * @deprecated As of 7.0.2, has not had any effect for a long time */ + @Deprecated public void captionSizeUpdated(Widget widget) { - componentCaptionSizeChanges.add(widget); + // This doesn't do anything, it's just kept here for compatibility } /** diff --git a/client/src/com/vaadin/client/communication/StateChangeEvent.java b/client/src/com/vaadin/client/communication/StateChangeEvent.java index 35187b03d4..e17a56aa69 100644 --- a/client/src/com/vaadin/client/communication/StateChangeEvent.java +++ b/client/src/com/vaadin/client/communication/StateChangeEvent.java @@ -19,10 +19,17 @@ import java.io.Serializable; import java.util.HashSet; import java.util.Set; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.json.client.JSONObject; import com.vaadin.client.FastStringSet; +import com.vaadin.client.JsArrayObject; +import com.vaadin.client.Profiler; import com.vaadin.client.ServerConnector; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.client.metadata.NoDataException; +import com.vaadin.client.metadata.Property; +import com.vaadin.client.ui.AbstractConnector; public class StateChangeEvent extends AbstractServerConnectorEvent<StateChangeHandler> { @@ -31,14 +38,24 @@ public class StateChangeEvent extends */ public static final Type<StateChangeHandler> TYPE = new Type<StateChangeHandler>(); - private final FastStringSet changedProperties; + /** + * Used to cache a FastStringSet representation of the properties that have + * changed if one is needed. + */ + @Deprecated + private FastStringSet changedProperties; /** * Used to cache a Set representation of the changedProperties if one is * needed. */ + @Deprecated private Set<String> changedPropertiesSet; + private boolean isNewConnector = false; + + private JSONObject stateJson; + @Override public Type<StateChangeHandler> getAssociatedType() { return TYPE; @@ -52,14 +69,16 @@ public class StateChangeEvent extends * @param changedPropertiesSet * a set of names of the changed properties * @deprecated As of 7.0.1, use - * {@link #StateChangeEvent(ServerConnector, FastStringSet)} + * {@link #StateChangeEvent(ServerConnector, JSONObject, boolean)} * instead for improved performance. */ @Deprecated public StateChangeEvent(ServerConnector connector, Set<String> changedPropertiesSet) { setConnector(connector); + // Keep instance around for caching this.changedPropertiesSet = changedPropertiesSet; + changedProperties = FastStringSet.create(); for (String property : changedPropertiesSet) { changedProperties.add(property); @@ -73,13 +92,35 @@ public class StateChangeEvent extends * the event whose state has changed * @param changedProperties * a set of names of the changed properties + * @deprecated As of 7.0.2, use + * {@link #StateChangeEvent(ServerConnector, JSONObject, boolean)} + * instead for improved performance. */ + @Deprecated public StateChangeEvent(ServerConnector connector, FastStringSet changedProperties) { setConnector(connector); this.changedProperties = changedProperties; } + /** + * /** Creates a new state change event. + * + * @param connector + * the event whose state has changed + * @param stateJson + * the JSON representation of the state change + * @param isNewConnector + * <code>true</code> if the state change is for a new connector, + * otherwise <code>false</code> + */ + public StateChangeEvent(ServerConnector connector, JSONObject stateJson, + boolean isNewConnector) { + setConnector(connector); + this.stateJson = stateJson; + this.isNewConnector = isNewConnector; + } + @Override public void dispatch(StateChangeHandler listener) { listener.onStateChanged(this); @@ -108,15 +149,16 @@ public class StateChangeEvent extends * * @return a set of names of the changed properties * - * @deprecated As of 7.0.1, use {@link #getChangedPropertiesFastSet()} or - * {@link #hasPropertyChanged(String)} instead for improved - * performance. + * @deprecated As of 7.0.1, use {@link #hasPropertyChanged(String)} instead + * for improved performance. */ @Deprecated public Set<String> getChangedProperties() { if (changedPropertiesSet == null) { + Profiler.enter("StateChangeEvent.getChangedProperties populate"); changedPropertiesSet = new HashSet<String>(); - changedProperties.addAllTo(changedPropertiesSet); + getChangedPropertiesFastSet().addAllTo(changedPropertiesSet); + Profiler.leave("StateChangeEvent.getChangedProperties populate"); } return changedPropertiesSet; } @@ -126,8 +168,24 @@ public class StateChangeEvent extends * * @return a set of names of the changed properties * + * @deprecated As of 7.0.1, use {@link #hasPropertyChanged(String)} instead + * for improved performance. */ + @Deprecated public FastStringSet getChangedPropertiesFastSet() { + if (changedProperties == null) { + Profiler.enter("StateChangeEvent.getChangedPropertiesFastSet populate"); + changedProperties = FastStringSet.create(); + + addJsonFields(stateJson, changedProperties, ""); + if (isNewConnector) { + addAllStateFields( + AbstractConnector.getStateType(getConnector()), + changedProperties, ""); + } + + Profiler.leave("StateChangeEvent.getChangedPropertiesFastSet populate"); + } return changedProperties; } @@ -140,6 +198,115 @@ public class StateChangeEvent extends * <code>false></code> */ public boolean hasPropertyChanged(String property) { - return changedProperties.contains(property); + if (isNewConnector) { + // Everything has changed for a new connector + return true; + } else if (stateJson != null) { + // Check whether it's in the json object + return isInJson(property, stateJson.getJavaScriptObject()); + } else { + // Legacy cases + if (changedProperties != null) { + // Check legacy stuff + return changedProperties.contains(property); + } else if (changedPropertiesSet != null) { + // Check legacy stuff + return changedPropertiesSet.contains(property); + } else { + throw new IllegalStateException( + "StateChangeEvent should have either stateJson, changedProperties or changePropertiesSet"); + } + } + } + + /** + * Checks whether the given property name (which might contains dots) is + * defined in some JavaScript object. + * + * @param property + * the name of the property, might include dots to reference + * inner objects + * @param target + * the JavaScript object to check + * @return true if the property is defined + */ + private static native final boolean isInJson(String property, + JavaScriptObject target) + /*-{ + var segments = property.split('.'); + while (typeof target == 'object') { + var nextSegment = segments.shift(); + if (!(nextSegment in target)) { + // Abort if segment is not found + return false; + } else if (segments.length == 0) { + // Done if there are no more segments + return true; + } else { + // Else just go deeper + target = target[nextSegment]; + } + } + // Not defined if we reach something that isn't an object + return false; + }-*/; + + /** + * Recursively adds the names of all properties in the provided state type. + * + * @param type + * the type to process + * @param changedProperties + * a set of all currently added properties + * @param context + * the base name of the current object + */ + @Deprecated + private static void addAllStateFields(com.vaadin.client.metadata.Type type, + FastStringSet changedProperties, String context) { + try { + JsArrayObject<Property> properties = type.getPropertiesAsArray(); + int size = properties.size(); + for (int i = 0; i < size; i++) { + Property property = properties.get(i); + String propertyName = context + property.getName(); + changedProperties.add(propertyName); + + com.vaadin.client.metadata.Type propertyType = property + .getType(); + if (propertyType.hasProperties()) { + addAllStateFields(propertyType, changedProperties, + propertyName + "."); + } + } + } catch (NoDataException e) { + throw new IllegalStateException("No property info for " + type + + ". Did you remember to compile the right widgetset?", e); + } + } + + /** + * Recursively adds the names of all fields in all objects in the provided + * json object. + * + * @param json + * the json object to process + * @param changedProperties + * a set of all currently added fields + * @param context + * the base name of the current object + */ + @Deprecated + private static void addJsonFields(JSONObject json, + FastStringSet changedProperties, String context) { + for (String key : json.keySet()) { + String fieldName = context + key; + changedProperties.add(fieldName); + + JSONObject object = json.get(key).isObject(); + if (object != null) { + addJsonFields(object, changedProperties, fieldName + "."); + } + } } } diff --git a/client/src/com/vaadin/client/metadata/Type.java b/client/src/com/vaadin/client/metadata/Type.java index 9c8a52d8e5..c09dffa638 100644 --- a/client/src/com/vaadin/client/metadata/Type.java +++ b/client/src/com/vaadin/client/metadata/Type.java @@ -17,6 +17,7 @@ package com.vaadin.client.metadata; import java.util.Collection; +import com.google.gwt.core.client.JsArrayString; import com.vaadin.client.JsArrayObject; import com.vaadin.client.communication.JSONSerializer; @@ -138,4 +139,8 @@ public class Type { return TypeDataStore.hasProperties(this); } + public JsArrayString getDelegateToWidgetProperties() { + return TypeDataStore.getDelegateToWidgetProperites(this); + } + } diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java index c1eca0a168..dff02749f8 100644 --- a/client/src/com/vaadin/client/metadata/TypeDataStore.java +++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collection; import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; import com.vaadin.client.FastStringMap; import com.vaadin.client.FastStringSet; import com.vaadin.client.JsArrayObject; @@ -35,6 +36,8 @@ public class TypeDataStore { .create(); private final FastStringMap<JsArrayObject<Property>> properties = FastStringMap .create(); + private final FastStringMap<JsArrayString> delegateToWidgetProperties = FastStringMap + .create(); private final FastStringSet delayedMethods = FastStringSet.create(); private final FastStringSet lastOnlyMethods = FastStringSet.create(); @@ -118,11 +121,22 @@ public class TypeDataStore { return get().delegateToWidget.get(property.getSignature()); } + public static JsArrayString getDelegateToWidgetProperites(Type type) { + return get().delegateToWidgetProperties.get(type.getSignature()); + } + public void setDelegateToWidget(Class<?> clazz, String propertyName, String delegateValue) { - delegateToWidget.put( - new Property(getType(clazz), propertyName).getSignature(), + Type type = getType(clazz); + delegateToWidget.put(new Property(type, propertyName).getSignature(), delegateValue); + JsArrayString typeProperties = delegateToWidgetProperties.get(type + .getSignature()); + if (typeProperties == null) { + typeProperties = JavaScriptObject.createArray().cast(); + delegateToWidgetProperties.put(type.getSignature(), typeProperties); + } + typeProperties.push(propertyName); } public void setReturnType(Class<?> type, String methodName, Type returnType) { diff --git a/common.xml b/common.xml index 57d96664f0..6e88bf6113 100644 --- a/common.xml +++ b/common.xml @@ -316,6 +316,7 @@ <exclude name="com/vaadin/tests/data/bean/*" /> <exclude name="com/vaadin/tests/util/*" /> <exclude name="**/VaadinClasses.java" /> + <exclude name="**TestRunner.java" /> <exclude name="**/SQLTestsConstants.java" /> </fileset> </batchtest> diff --git a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java index 55bd3a4641..502394f38a 100644 --- a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java +++ b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java @@ -143,8 +143,7 @@ public class BeanFieldGroup<T> extends FieldGroup { return (BeanItem<T>) super.getItemDataSource(); } - @Override - public void bind(Field field, Object propertyId) { + private void ensureNestedPropertyAdded(Object propertyId) { if (getItemDataSource() != null) { // The data source is set so the property must be found in the item. // If it is not we try to add it. @@ -156,11 +155,22 @@ public class BeanFieldGroup<T> extends FieldGroup { getItemDataSource().addNestedProperty((String) propertyId); } } + } + @Override + public void bind(Field field, Object propertyId) { + ensureNestedPropertyAdded(propertyId); super.bind(field, propertyId); } @Override + public Field<?> buildAndBind(String caption, Object propertyId) + throws BindException { + ensureNestedPropertyAdded(propertyId); + return super.buildAndBind(caption, propertyId); + } + + @Override protected void configureField(Field<?> field) { super.configureField(field); // Add Bean validators if there are annotations diff --git a/server/src/com/vaadin/data/util/AbstractProperty.java b/server/src/com/vaadin/data/util/AbstractProperty.java index 9eafca6051..499421a8b4 100644 --- a/server/src/com/vaadin/data/util/AbstractProperty.java +++ b/server/src/com/vaadin/data/util/AbstractProperty.java @@ -18,6 +18,7 @@ package com.vaadin.data.util; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; +import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.data.Property; @@ -81,11 +82,11 @@ public abstract class AbstractProperty<T> implements Property<T>, @Override public String toString() { getLogger() - .warning( - "You are using Property.toString() instead of getValue() to get the value for a " - + getClass().getSimpleName() - + ". This will not be supported starting from Vaadin 7.1 " - + "(your debugger might call toString() and cause this message to appear)."); + .log(Level.WARNING, + "You are using Property.toString() instead of getValue() to get the value for a {0}." + + "This will not be supported starting from Vaadin 7.1 " + + "(your debugger might call toString() and cause this message to appear).", + getClass().getSimpleName()); T v = getValue(); if (v == null) { return null; diff --git a/server/src/com/vaadin/data/util/IndexedContainer.java b/server/src/com/vaadin/data/util/IndexedContainer.java index 306f9cf23b..1df4dd9bfb 100644 --- a/server/src/com/vaadin/data/util/IndexedContainer.java +++ b/server/src/com/vaadin/data/util/IndexedContainer.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.data.Container; @@ -966,11 +967,11 @@ public class IndexedContainer extends @Override public String toString() { getLogger() - .warning( - "You are using IndexedContainerProperty.toString() instead of getValue() to get the value for a " - + getClass().getSimpleName() - + ". This will not be supported starting from Vaadin 7.1 " - + "(your debugger might call toString() and cause this message to appear)."); + .log(Level.WARNING, + "You are using IndexedContainerProperty.toString() instead of getValue() to get the value for a {0}." + + " This will not be supported starting from Vaadin 7.1 " + + "(your debugger might call toString() and cause this message to appear).", + getClass().getSimpleName()); Object v = getValue(); if (v == null) { return null; diff --git a/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java b/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java index 785c7b22af..378372d044 100644 --- a/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java +++ b/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java @@ -18,6 +18,7 @@ package com.vaadin.data.util.sqlcontainer; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; +import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.data.Property; @@ -264,11 +265,11 @@ final public class ColumnProperty implements Property { @Override public String toString() { getLogger() - .warning( - "You are using ColumnProperty.toString() instead of getValue() to get the value for a " - + getClass().getSimpleName() - + ". This will not be supported starting from Vaadin 7.1 " - + "(your debugger might call toString() and cause this message to appear)."); + .log(Level.WARNING, + "You are using ColumnProperty.toString() instead of getValue() to get the value for a {0}. " + + "This will not be supported starting from Vaadin 7.1 (your debugger might call toString() " + + "and cause this message to appear).", + getClass().getSimpleName()); Object v = getValue(); if (v == null) { return null; diff --git a/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java b/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java index 4d60a8cc17..63e4b3362c 100644 --- a/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java +++ b/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java @@ -610,8 +610,10 @@ public class TableQuery implements QueryDelegate, @Override public boolean removeRow(RowItem row) throws UnsupportedOperationException, SQLException { - getLogger().log(Level.FINE, "Removing row with id: {0}", - row.getId().getId()[0].toString()); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log(Level.FINE, "Removing row with id: {0}", + row.getId().getId()[0]); + } if (executeUpdate(sqlGenerator.generateDeleteQuery(getTableName(), primaryKeyColumns, versionColumn, row)) == 1) { return true; diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index cd7279e601..17bbbda737 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -791,10 +791,8 @@ public abstract class AbstractCommunicationManager implements Serializable { dirtyVisibleConnectors .addAll(getDirtyVisibleConnectors(uiConnectorTracker)); - getLogger().log( - Level.FINE, - "Found " + dirtyVisibleConnectors.size() - + " dirty connectors to paint"); + getLogger().log(Level.FINE, "Found {0} dirty connectors to paint", + dirtyVisibleConnectors.size()); for (ClientConnector connector : dirtyVisibleConnectors) { boolean initialized = uiConnectorTracker .isClientSideInitialized(connector); @@ -1226,10 +1224,10 @@ public abstract class AbstractCommunicationManager implements Serializable { null, stateType, uI.getConnectorTracker()); diffState = encodeResult.getEncodedValue(); } catch (Exception e) { - getLogger().log( - Level.WARNING, - "Error creating reference object for state of type " - + stateType.getName()); + getLogger() + .log(Level.WARNING, + "Error creating reference object for state of type {0}", + stateType.getName()); } } EncodeResult encodeResult = JsonCodec.encode(state, diffState, @@ -1276,10 +1274,10 @@ public abstract class AbstractCommunicationManager implements Serializable { if (publishedFileContexts.containsKey(name)) { Class<?> oldContext = publishedFileContexts.get(name); if (oldContext != context) { - getLogger().warning( - name + " published by both " + context + " and " - + oldContext + ". File from " + oldContext - + " will be used."); + getLogger() + .log(Level.WARNING, + "{0} published by both {1} and {2}. File from {2} will be used.", + new Object[] { name, context, oldContext }); } } else { publishedFileContexts.put(name, context); @@ -1312,9 +1310,13 @@ public abstract class AbstractCommunicationManager implements Serializable { } sortByHierarchy((List) legacyComponents); for (LegacyComponent c : legacyComponents) { - getLogger().fine( - "Painting LegacyComponent " + c.getClass().getName() + "@" - + Integer.toHexString(c.hashCode())); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log( + Level.FINE, + "Painting LegacyComponent {0}@{1}", + new Object[] { c.getClass().getName(), + Integer.toHexString(c.hashCode()) }); + } paintTarget.startTag("change"); final String pid = c.getConnectorId(); paintTarget.addAttribute("pid", pid); @@ -1705,9 +1707,9 @@ public abstract class AbstractCommunicationManager implements Serializable { } } } catch (JSONException e) { - getLogger().warning( - "Unable to parse RPC call from the client: " - + e.getMessage()); + getLogger().log(Level.WARNING, + "Unable to parse RPC call from the client: {0}", + e.getMessage()); // TODO or return success = false? throw new RuntimeException(e); } @@ -1853,11 +1855,11 @@ public abstract class AbstractCommunicationManager implements Serializable { * corresponding to the received method invocation has been * registered. */ - getLogger().warning( - "Ignoring RPC call to " + interfaceName + "." + methodName - + " in connector " + connector.getClass().getName() - + "(" + connectorId - + ") as no RPC implementation is regsitered"); + getLogger() + .log(Level.WARNING, + "Ignoring RPC call to {0}.{1} in connector {2} ({3}) as no RPC implementation is regsitered", + new Object[] { interfaceName, methodName, + connector.getClass().getName(), connectorId }); return null; } @@ -2056,9 +2058,8 @@ public abstract class AbstractCommunicationManager implements Serializable { DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, l); if (!(dateFormat instanceof SimpleDateFormat)) { - getLogger().warning( - "Unable to get default date pattern for locale " - + l.toString()); + getLogger().log(Level.WARNING, + "Unable to get default date pattern for locale {0}", l); dateFormat = new SimpleDateFormat(); } final String df = ((SimpleDateFormat) dateFormat).toPattern(); @@ -2218,8 +2219,10 @@ public abstract class AbstractCommunicationManager implements Serializable { if (id == null) { id = nextTypeKey++; typeToKey.put(class1, id); - getLogger().log(Level.FINE, - "Mapping " + class1.getName() + " to " + id); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log(Level.FINE, "Mapping {0} to {1}", + new Object[] { class1.getName(), id }); + } } return id.toString(); } @@ -2434,11 +2437,12 @@ public abstract class AbstractCommunicationManager implements Serializable { reinitUI(retainedUI, request); return retainedUI; } else { - getLogger().info( - "Not using retained UI in " + windowName - + " because retained UI was of type " - + retainedUI.getClass() + " but " + uiClass - + " is expected for the request."); + getLogger().log( + Level.INFO, + "Not using retained UI in {0} because retained UI was of type {1}" + + " but {2} is expected for the request.", + new Object[] { windowName, retainedUI.getClass(), + uiClass }); } } } @@ -2469,9 +2473,10 @@ public abstract class AbstractCommunicationManager implements Serializable { if (vaadinService.preserveUIOnRefresh(provider, event)) { // Remember this UI if (windowName == null) { - getLogger().warning( - "There is no window.name available for UI " + uiClass - + " that should be preserved."); + getLogger() + .log(Level.WARNING, + "There is no window.name available for UI {0} that should be preserved.", + uiClass); } else { session.getPreserveOnRefreshUIs().put(windowName, uiId); } @@ -2523,7 +2528,7 @@ public abstract class AbstractCommunicationManager implements Serializable { writeUidlResponse(request, true, pWriter, uI, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); - getLogger().log(Level.FINE, "Initial UIDL:" + initialUIDL); + getLogger().log(Level.FINE, "Initial UIDL:{0}", initialUIDL); return initialUIDL; } @@ -2554,9 +2559,9 @@ public abstract class AbstractCommunicationManager implements Serializable { // Security check: avoid accidentally serving from the UI of the // classpath instead of relative to the context class if (fileName.startsWith("/")) { - getLogger().warning( - "Published file request starting with / rejected: " - + fileName); + getLogger().log(Level.WARNING, + "Published file request starting with / rejected: {0}", + fileName); response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName); return; } @@ -2570,9 +2575,10 @@ public abstract class AbstractCommunicationManager implements Serializable { // Security check: don't serve resource if the name hasn't been // registered in the map if (context == null) { - getLogger().warning( - "Rejecting published file request for file that has not been published: " - + fileName); + getLogger() + .log(Level.WARNING, + "Rejecting published file request for file that has not been published: {0}", + fileName); response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName); return; } @@ -2580,12 +2586,14 @@ public abstract class AbstractCommunicationManager implements Serializable { // Resolve file relative to the location of the context class InputStream in = context.getResourceAsStream(fileName); if (in == null) { - getLogger().warning( - fileName + " published by " + context.getName() - + " not found. Verify that the file " - + context.getPackage().getName().replace('.', '/') - + '/' + fileName - + " is available on the classpath."); + getLogger() + .log(Level.WARNING, + "{0} published by {1} not found. Verify that the file {2}/{3} is available on the classpath.", + new Object[] { + fileName, + context.getName(), + context.getPackage().getName() + .replace('.', '/'), fileName }); response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName); return; } diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java index a3690cf040..5a54b5ae3a 100644 --- a/server/src/com/vaadin/server/DragAndDropService.java +++ b/server/src/com/vaadin/server/DragAndDropService.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONException; @@ -97,9 +98,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { DropHandler dropHandler = (dropTarget).getDropHandler(); if (dropHandler == null) { // No dropHandler returned so no drop can be performed. - getLogger().fine( - "DropTarget.getDropHandler() returned null for owner: " - + dropTarget); + getLogger().log(Level.FINE, + "DropTarget.getDropHandler() returned null for owner: {0}", + dropTarget); return; } diff --git a/server/src/com/vaadin/server/GAEVaadinServlet.java b/server/src/com/vaadin/server/GAEVaadinServlet.java index 140ed54828..0d2063d446 100644 --- a/server/src/com/vaadin/server/GAEVaadinServlet.java +++ b/server/src/com/vaadin/server/GAEVaadinServlet.java @@ -272,7 +272,8 @@ public class GAEVaadinServlet extends VaadinServlet { ds.put(entity); } catch (DeadlineExceededException e) { - getLogger().warning("DeadlineExceeded for " + session.getId()); + getLogger().log(Level.WARNING, "DeadlineExceeded for {0}", + session.getId()); sendDeadlineExceededNotification(request, response); } catch (NotSerializableException e) { getLogger().log(Level.SEVERE, "Not serializable!", e); @@ -421,9 +422,10 @@ public class GAEVaadinServlet extends VaadinServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired Vaadin sessions."); + getLogger() + .log(Level.INFO, + "Vaadin cleanup deleting {0} expired Vaadin sessions.", + entities.size()); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); @@ -441,9 +443,10 @@ public class GAEVaadinServlet extends VaadinServlet { List<Entity> entities = pq.asList(Builder .withLimit(CLEANUP_LIMIT)); if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired appengine sessions."); + getLogger() + .log(Level.INFO, + "Vaadin cleanup deleting {0} expired appengine sessions.", + entities.size()); List<Key> keys = new ArrayList<Key>(); for (Entity e : entities) { keys.add(e.getKey()); diff --git a/server/src/com/vaadin/server/JsonPaintTarget.java b/server/src/com/vaadin/server/JsonPaintTarget.java index 12d2c93272..11bfb33fe1 100644 --- a/server/src/com/vaadin/server/JsonPaintTarget.java +++ b/server/src/com/vaadin/server/JsonPaintTarget.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.Vector; +import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.ui.Alignment; @@ -663,9 +664,13 @@ public class JsonPaintTarget implements PaintTarget { throws PaintException { boolean topLevelPaintable = openPaintables.isEmpty(); - getLogger().fine( - "startPaintable for " + connector.getClass().getName() + "@" - + Integer.toHexString(connector.hashCode())); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log( + Level.FINE, + "startPaintable for {0}@{1}", + new Object[] { connector.getClass().getName(), + Integer.toHexString(connector.hashCode()) }); + } startTag(tagName, true); openPaintables.push(connector); @@ -687,9 +692,13 @@ public class JsonPaintTarget implements PaintTarget { @Override public void endPaintable(Component paintable) throws PaintException { - getLogger().fine( - "endPaintable for " + paintable.getClass().getName() + "@" - + Integer.toHexString(paintable.hashCode())); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log( + Level.FINE, + "endPaintable for {0}@{1}", + new Object[] { paintable.getClass().getName(), + Integer.toHexString(paintable.hashCode()) }); + } ClientConnector openPaintable = openPaintables.peek(); if (paintable != openPaintable) { diff --git a/server/src/com/vaadin/server/ServerRpcManager.java b/server/src/com/vaadin/server/ServerRpcManager.java index d712eafe37..ec25ce83ca 100644 --- a/server/src/com/vaadin/server/ServerRpcManager.java +++ b/server/src/com/vaadin/server/ServerRpcManager.java @@ -119,11 +119,9 @@ public class ServerRpcManager<T extends ServerRpc> implements Serializable { } else { getLogger() .log(Level.WARNING, - "RPC call received for RpcTarget " - + target.getClass().getName() - + " (" - + invocation.getConnectorId() - + ") but the target has not registered any RPC interfaces"); + "RPC call received for RpcTarget {0} ({1}) but the target has not registered any RPC interfaces", + new Object[] { target.getClass().getName(), + invocation.getConnectorId() }); } } diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java index e13a64682e..b4a2390fa5 100644 --- a/server/src/com/vaadin/server/VaadinPortlet.java +++ b/server/src/com/vaadin/server/VaadinPortlet.java @@ -28,6 +28,7 @@ import java.security.GeneralSecurityException; import java.util.Enumeration; import java.util.Map; import java.util.Properties; +import java.util.logging.Level; import java.util.logging.Logger; import javax.portlet.ActionRequest; @@ -606,9 +607,8 @@ public class VaadinPortlet extends GenericPortlet implements Constants, os.write(buffer, 0, bytes); } } else { - getLogger().info( - "Requested resource [" + resourceID - + "] could not be found"); + getLogger().log(Level.INFO, + "Requested resource [{0}] could not be found", resourceID); response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_NOT_FOUND)); } diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index d35cf74262..ada0fac107 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -25,6 +25,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; +import java.util.logging.Level; import java.util.logging.Logger; import javax.portlet.PortletContext; @@ -786,9 +787,8 @@ public abstract class VaadinService implements Serializable { if (!session.isClosing()) { closeSession(session); if (session.getSession() != null) { - getLogger().fine( - "Closing inactive session " - + session.getSession().getId()); + getLogger().log(Level.FINE, "Closing inactive session {0}", + session.getSession().getId()); } } if (session.getSession() != null) { @@ -817,7 +817,8 @@ public abstract class VaadinService implements Serializable { private void removeClosedUIs(VaadinSession session) { for (UI ui : new ArrayList<UI>(session.getUIs())) { if (ui.isClosing()) { - getLogger().finer("Removing closed UI " + ui.getUIId()); + getLogger().log(Level.FINER, "Removing closed UI {0}", + ui.getUIId()); session.removeUI(ui); } } @@ -833,9 +834,9 @@ public abstract class VaadinService implements Serializable { String sessionId = session.getSession().getId(); for (UI ui : session.getUIs()) { if (!isUIActive(ui) && !ui.isClosing()) { - getLogger().fine( - "Closing inactive UI #" + ui.getUIId() + " in session " - + sessionId); + getLogger().log(Level.FINE, + "Closing inactive UI #{0} in session {1}", + new Object[] { ui.getUIId(), sessionId }); ui.close(); } } diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index b3f9b36a83..4fde870f74 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -792,10 +792,10 @@ public class VaadinServlet extends HttpServlet implements Constants { } else { // cannot serve requested file getLogger() - .info("Requested resource [" - + filename - + "] not found from filesystem or through class loader." - + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder."); + .log(Level.INFO, + "Requested resource [{0}] not found from filesystem or through class loader." + + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.", + filename); response.setStatus(HttpServletResponse.SC_NOT_FOUND); } return; @@ -805,9 +805,9 @@ public class VaadinServlet extends HttpServlet implements Constants { // directory if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { getLogger() - .info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); + .log(Level.INFO, + "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.", + filename); response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } @@ -945,9 +945,9 @@ public class VaadinServlet extends HttpServlet implements Constants { // directory if (!isAllowedVAADINResourceUrl(request, scssUrl)) { getLogger() - .info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); + .log(Level.INFO, + "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.", + filename); response.setStatus(HttpServletResponse.SC_FORBIDDEN); // Handled, return true so no further processing is done return true; @@ -968,16 +968,14 @@ public class VaadinServlet extends HttpServlet implements Constants { if (scss == null) { getLogger() - .warning( - "Scss file " - + scssFilename - + " exists but ScssStylesheet was not able to find it"); + .log(Level.WARNING, + "Scss file {0} exists but ScssStylesheet was not able to find it", + scssFilename); return false; } try { - getLogger().fine( - "Compiling " + realFilename + " for request to " - + filename); + getLogger().log(Level.FINE, "Compiling {0} for request to {1}", + new Object[] { realFilename, filename }); scss.compile(); } catch (Exception e) { e.printStackTrace(); @@ -1028,14 +1026,15 @@ public class VaadinServlet extends HttpServlet implements Constants { // loader sees it. if (!resourceUrl.getPath().contains("!/VAADIN/")) { - getLogger().info( - "Blocked attempt to access a JAR entry not starting with /VAADIN/: " - + resourceUrl); + getLogger() + .log(Level.INFO, + "Blocked attempt to access a JAR entry not starting with /VAADIN/: {0}", + resourceUrl); return false; } - getLogger().fine( - "Accepted access to a JAR entry using a class loader: " - + resourceUrl); + getLogger().log(Level.FINE, + "Accepted access to a JAR entry using a class loader: {0}", + resourceUrl); return true; } else { // Some servers such as GlassFish extract files from JARs (file:) @@ -1045,13 +1044,13 @@ public class VaadinServlet extends HttpServlet implements Constants { // "/../" if (!resourceUrl.getPath().contains("/VAADIN/") || resourceUrl.getPath().contains("/../")) { - getLogger().info( - "Blocked attempt to access file: " + resourceUrl); + getLogger().log(Level.INFO, + "Blocked attempt to access file: {0}", resourceUrl); return false; } - getLogger().fine( - "Accepted access to a file using a class loader: " - + resourceUrl); + getLogger().log(Level.FINE, + "Accepted access to a file using a class loader: {0}", + resourceUrl); return true; } } diff --git a/server/src/com/vaadin/ui/AbstractMedia.java b/server/src/com/vaadin/ui/AbstractMedia.java index 55464aeb8d..41677467bb 100644 --- a/server/src/com/vaadin/ui/AbstractMedia.java +++ b/server/src/com/vaadin/ui/AbstractMedia.java @@ -19,6 +19,7 @@ package com.vaadin.ui; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -88,9 +89,9 @@ public abstract class AbstractMedia extends AbstractComponent { int sourceIndex = Integer.parseInt(matcher.group(1)); if (sourceIndex < 0 || sourceIndex >= sources.size()) { - getLogger().warning( - "Requested source index " + sourceIndex - + " is out of bounds"); + getLogger().log(Level.WARNING, + "Requested source index {0} is out of bounds", + sourceIndex); return false; } diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java index a888f9f7eb..a229003224 100644 --- a/server/src/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/com/vaadin/ui/ConnectorTracker.java @@ -120,17 +120,23 @@ public class ConnectorTracker implements Serializable { if (previouslyRegistered == null) { connectorIdToConnector.put(connectorId, connector); uninitializedConnectors.add(connector); - getLogger().fine( - "Registered " + connector.getClass().getSimpleName() + " (" - + connectorId + ")"); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log( + Level.FINE, + "Registered {0} ({1})", + new Object[] { connector.getClass().getSimpleName(), + connectorId }); + } } else if (previouslyRegistered != connector) { throw new RuntimeException("A connector with id " + connectorId + " is already registered!"); } else if (!wasUnregistered) { - getLogger().warning( - "An already registered connector was registered again: " - + connector.getClass().getSimpleName() + " (" - + connectorId + ")"); + getLogger() + .log(Level.WARNING, + "An already registered connector was registered again: {0} ({1})", + new Object[] { + connector.getClass().getSimpleName(), + connectorId }); } dirtyConnectors.add(connector); } @@ -149,10 +155,11 @@ public class ConnectorTracker implements Serializable { public void unregisterConnector(ClientConnector connector) { String connectorId = connector.getConnectorId(); if (!connectorIdToConnector.containsKey(connectorId)) { - getLogger().warning( - "Tried to unregister " - + connector.getClass().getSimpleName() + " (" - + connectorId + ") which is not registered"); + getLogger().log( + Level.WARNING, + "Tried to unregister {0} ({1}) which is not registered", + new Object[] { connector.getClass().getSimpleName(), + connectorId }); return; } if (connectorIdToConnector.get(connectorId) != connector) { @@ -163,14 +170,19 @@ public class ConnectorTracker implements Serializable { dirtyConnectors.remove(connector); if (unregisteredConnectors.add(connector)) { - getLogger().fine( - "Unregistered " + connector.getClass().getSimpleName() - + " (" + connectorId + ")"); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log( + Level.FINE, + "Unregistered {0} ({1})", + new Object[] { connector.getClass().getSimpleName(), + connectorId }); + } } else { - getLogger().warning( - "Unregistered " + connector.getClass().getSimpleName() - + " (" + connectorId - + ") that was already unregistered."); + getLogger().log( + Level.WARNING, + "Unregistered {0} ({1}) that was already unregistered.", + new Object[] { connector.getClass().getSimpleName(), + connectorId }); } } @@ -273,11 +285,11 @@ public class ConnectorTracker implements Serializable { // This code should never be called as cleanup should take place // in detach() + getLogger() - .warning( - "cleanConnectorMap unregistered connector " - + getConnectorAndParentInfo(connector) - + "). This should have been done when the connector was detached."); + .log(Level.WARNING, + "cleanConnectorMap unregistered connector {0}. This should have been done when the connector was detached.", + getConnectorAndParentInfo(connector)); removeFromGlobalResourceHandler(connector); uninitializedConnectors.remove(connector); @@ -288,10 +300,12 @@ public class ConnectorTracker implements Serializable { && !uninitializedConnectors.contains(connector)) { uninitializedConnectors.add(connector); diffStates.remove(connector); - getLogger().fine( - "cleanConnectorMap removed state for " - + getConnectorAndParentInfo(connector) - + " as it is not visible"); + if (getLogger().isLoggable(Level.FINE)) { + getLogger() + .log(Level.FINE, + "cleanConnectorMap removed state for {0} as it is not visible", + getConnectorAndParentInfo(connector)); + } } } @@ -335,9 +349,8 @@ public class ConnectorTracker implements Serializable { if (getLogger().isLoggable(Level.FINE)) { if (!dirtyConnectors.contains(connector)) { - getLogger().fine( - getConnectorAndParentInfo(connector) + " " - + "is now dirty"); + getLogger().log(Level.FINE, "{0} is now dirty", + getConnectorAndParentInfo(connector)); } } @@ -353,9 +366,8 @@ public class ConnectorTracker implements Serializable { public void markClean(ClientConnector connector) { if (getLogger().isLoggable(Level.FINE)) { if (dirtyConnectors.contains(connector)) { - getLogger().fine( - getConnectorAndParentInfo(connector) + " " - + "is no longer dirty"); + getLogger().log(Level.FINE, "{0} is no longer dirty", + getConnectorAndParentInfo(connector)); } } diff --git a/server/src/com/vaadin/ui/Tree.java b/server/src/com/vaadin/ui/Tree.java index 32c5712f0f..a6dbea51ba 100644 --- a/server/src/com/vaadin/ui/Tree.java +++ b/server/src/com/vaadin/ui/Tree.java @@ -75,7 +75,7 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, /** * Set of expanded nodes. */ - private final HashSet<Object> expanded = new HashSet<Object>(); + private HashSet<Object> expanded = new HashSet<Object>(); /** * List of action handlers. @@ -845,9 +845,20 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, newDataSource)); } - // Ensure previous expanded items are cleaned up if they don't exist in - // the new container - cleanupExpandedItems(); + /* + * Ensure previous expanded items are cleaned up if they don't exist in + * the new container + */ + if (expanded != null) { + /* + * We need to check that the expanded-field is not null since + * setContainerDataSource() is called from the parent constructor + * (AbstractSelect()) and at that time the expanded field is not yet + * initialized. + */ + cleanupExpandedItems(); + } + } /* Expand event and listener */ @@ -1652,7 +1663,6 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, target.addAttribute("depth", depthToCheck); target.addAttribute("key", key(rootId)); } - } /** @@ -1677,24 +1687,16 @@ public class Tree extends AbstractSelect implements Container.Hierarchical, return itemDescriptionGenerator; } - @Override - public void containerItemSetChange( - com.vaadin.data.Container.ItemSetChangeEvent event) { - - // Ensure removed items are cleaned up from expanded list - cleanupExpandedItems(); - - super.containerItemSetChange(event); - } - private void cleanupExpandedItems() { + Set<Object> removedItemIds = new HashSet<Object>(); for (Object expandedItemId : expanded) { if (getItem(expandedItemId) == null) { - expanded.remove(expandedItemId); + removedItemIds.add(expandedItemId); if (this.expandedItemId == expandedItemId) { this.expandedItemId = null; } } } + expanded.removeAll(removedItemIds); } } diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 7e54aa01a0..d12c8d89c9 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -43,6 +43,7 @@ import com.vaadin.shared.ui.ui.ScrollClientRpc; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.shared.ui.ui.UIServerRpc; import com.vaadin.shared.ui.ui.UIState; +import com.vaadin.ui.Component.Focusable; import com.vaadin.util.CurrentInstance; /** diff --git a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java index 0d3dea28ac..3172b759a1 100644 --- a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java +++ b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java @@ -48,6 +48,11 @@ public class TestClassesSerializable extends TestCase { "com\\.vaadin\\.server\\.AbstractCommunicationManager.*", // "com\\.vaadin\\.server\\.CommunicationManager.*", // "com\\.vaadin\\.server\\.PortletCommunicationManager.*", // + "com\\.vaadin\\.buildhelpers.*", // + "com\\.vaadin\\.util\\.ReflectTools.*", // + "com\\.vaadin\\.data\\.util\\.ReflectTools.*", // + "com\\.vaadin\\.sass.*", // + "com\\.vaadin\\.util\\.CurrentInstance\\$1", // }; /** diff --git a/server/tests/src/com/vaadin/tests/server/component/fieldgroup/BeanFieldGroupTest.java b/server/tests/src/com/vaadin/tests/server/component/fieldgroup/BeanFieldGroupTest.java new file mode 100644 index 0000000000..68c1133dc0 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/fieldgroup/BeanFieldGroupTest.java @@ -0,0 +1,41 @@ +package com.vaadin.tests.server.component.fieldgroup; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import com.vaadin.data.fieldgroup.BeanFieldGroup; + +public class BeanFieldGroupTest { + + public static class MyBean { + + private MyNestedBean nestedBean = new MyNestedBean(); + + public MyNestedBean getNestedBean() { + return nestedBean; + } + } + + public static class MyNestedBean { + + private String hello = "Hello world"; + + public String getHello() { + return hello; + } + } + + @Test + public void buildAndBindNestedProperty() { + + MyBean bean = new MyBean(); + + BeanFieldGroup<MyBean> bfg = new BeanFieldGroup<MyBean>(MyBean.class); + bfg.setItemDataSource(bean); + + com.vaadin.ui.Field<?> helloField = bfg.buildAndBind("Hello string", + "nestedBean.hello"); + assertEquals(bean.nestedBean.hello, helloField.getValue().toString()); + } + +} diff --git a/theme-compiler/src/com/vaadin/sass/SassCompiler.java b/theme-compiler/src/com/vaadin/sass/SassCompiler.java index 840badfc25..48b2d24c46 100644 --- a/theme-compiler/src/com/vaadin/sass/SassCompiler.java +++ b/theme-compiler/src/com/vaadin/sass/SassCompiler.java @@ -17,6 +17,7 @@ package com.vaadin.sass; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; @@ -48,6 +49,12 @@ public class SassCompiler { // ScssStylesheet.setStylesheetResolvers(new VaadinResolver()); ScssStylesheet scss = ScssStylesheet.get(input); + if(scss == null){ + System.err.println("The scss file " + input + + " could not be found."); + return; + } + scss.compile(); if (output == null) { System.out.println(scss.toString()); diff --git a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java index 64279ad1e7..dbb3e571dc 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java +++ b/theme-compiler/src/com/vaadin/sass/internal/ScssStylesheet.java @@ -42,6 +42,7 @@ import com.vaadin.sass.internal.tree.MixinDefNode; import com.vaadin.sass.internal.tree.Node; import com.vaadin.sass.internal.tree.VariableNode; import com.vaadin.sass.internal.tree.controldirective.IfElseDefNode; +import com.vaadin.sass.internal.visitor.ExtendNodeHandler; import com.vaadin.sass.internal.visitor.ImportNodeHandler; public class ScssStylesheet extends Node { @@ -77,7 +78,8 @@ public class ScssStylesheet extends Node { * ScssStylesheet tree out of it. Calling compile() on it will transform * SASS into CSS. Calling toString() will print out the SCSS/CSS. * - * @param file + * @param identifier + * The file path. If null then null is returned. * @return * @throws CSSException * @throws IOException @@ -92,7 +94,8 @@ public class ScssStylesheet extends Node { * builds up a ScssStylesheet tree out of it. Calling compile() on it will * transform SASS into CSS. Calling toString() will print out the SCSS/CSS. * - * @param file + * @param identifier + * The file path. If null then null is returned. * @param encoding * @return * @throws CSSException @@ -108,6 +111,12 @@ public class ScssStylesheet extends Node { * * @charset declaration, the default one is ASCII. */ + + if (identifier == null) { + return null; + } + + // FIXME Is this actually intended? /John 1.3.2013 File file = new File(identifier); file = file.getCanonicalFile(); @@ -172,6 +181,7 @@ public class ScssStylesheet extends Node { variables.clear(); ifElseDefNodes.clear(); lastNodeAdded.clear(); + ExtendNodeHandler.clear(); importOtherFiles(this); populateDefinitions(this); traverse(this); diff --git a/theme-compiler/src/com/vaadin/sass/internal/util/StringUtil.java b/theme-compiler/src/com/vaadin/sass/internal/util/StringUtil.java index cf227fe3a3..b20e8bab61 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/util/StringUtil.java +++ b/theme-compiler/src/com/vaadin/sass/internal/util/StringUtil.java @@ -17,8 +17,10 @@ package com.vaadin.sass.internal.util; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; @@ -145,14 +147,7 @@ public class StringUtil { * @return true if the text contains the SCSS variable, false if not */ public static boolean containsVariable(String text, String varName) { - StringBuilder builder = new StringBuilder(); - // (?![\\w-]) means lookahead, the next one shouldn't be a word - // character nor a dash. - builder.append("\\$").append(Pattern.quote(varName)) - .append("(?![\\w-])"); - Pattern pattern = Pattern.compile(builder.toString()); - Matcher matcher = pattern.matcher(text); - return matcher.find(); + return containsSubString(text, "$" + varName); } /** @@ -162,18 +157,81 @@ public class StringUtil { * @param text * text which contains the SCSS variable * @param varName - * SCSS variable name + * SCSS variable name (Without '$' sign) * @param value * the value of the SCSS variable * @return the String after replacing */ public static String replaceVariable(String text, String varName, String value) { + return replaceSubString(text, "$" + varName, value); + } + + /** + * Check if a String contains a sub string, using whole word match. + * + * @param text + * text to be checked + * @Param sub Sub String to be checked. + * @return true if the text contains the sub string, false if not + */ + public static boolean containsSubString(String text, String sub) { StringBuilder builder = new StringBuilder(); // (?![\\w-]) means lookahead, the next one shouldn't be a word // character nor a dash. - builder.append("\\$").append(Pattern.quote(varName)) + builder.append("(?<![\\w-])").append(Pattern.quote(sub)) + .append("(?![\\w-])"); + Pattern pattern = Pattern.compile(builder.toString()); + Matcher matcher = pattern.matcher(text); + return matcher.find(); + } + + /** + * Replace the sub string in a String to a value, using whole word match. + * + * @param text + * text which contains the sub string + * @param sub + * the sub string + * @param value + * the new value + * @return the String after replacing + */ + public static String replaceSubString(String text, String sub, String value) { + StringBuilder builder = new StringBuilder(); + // (?![\\w-]) means lookahead, the next one shouldn't be a word + // character nor a dash. + builder.append("(?<![\\w-])").append(Pattern.quote(sub)) .append("(?![\\w-])"); return text.replaceAll(builder.toString(), value); } + + /** + * Remove duplicated sub string in a String given a splitter. Can be used to + * removed duplicated selectors, e.g., in ".error.error", one duplicated + * ".error" can be removed. + * + * @param motherString + * string which may contains duplicated sub strings + * @param splitter + * the splitter splits the mother string to sub strings + * @return the mother string with duplicated sub strings removed + */ + public static String removeDuplicatedSubString(String motherString, + String splitter) { + List<String> subStrings = Arrays.asList(motherString.split(Pattern + .quote(splitter))); + LinkedHashSet<String> uniqueSubStrings = new LinkedHashSet<String>( + subStrings); + StringBuilder builder = new StringBuilder(); + int count = 0; + for (String uniqueSubString : uniqueSubStrings) { + count++; + builder.append(uniqueSubString); + if (count < uniqueSubStrings.size()) { + builder.append(splitter); + } + } + return builder.toString(); + } } diff --git a/theme-compiler/src/com/vaadin/sass/internal/visitor/ExtendNodeHandler.java b/theme-compiler/src/com/vaadin/sass/internal/visitor/ExtendNodeHandler.java index f7917fff6e..e4a69ea5f3 100644 --- a/theme-compiler/src/com/vaadin/sass/internal/visitor/ExtendNodeHandler.java +++ b/theme-compiler/src/com/vaadin/sass/internal/visitor/ExtendNodeHandler.java @@ -26,6 +26,7 @@ import com.vaadin.sass.internal.ScssStylesheet; import com.vaadin.sass.internal.tree.BlockNode; import com.vaadin.sass.internal.tree.ExtendNode; import com.vaadin.sass.internal.tree.Node; +import com.vaadin.sass.internal.util.StringUtil; public class ExtendNodeHandler { private static Map<String, List<ArrayList<String>>> extendsMap = new HashMap<String, List<ArrayList<String>>>(); @@ -35,6 +36,12 @@ public class ExtendNodeHandler { modifyTree(ScssStylesheet.get()); } + public static void clear() { + if (extendsMap != null) { + extendsMap.clear(); + } + } + private static void modifyTree(Node node) throws Exception { for (Node child : node.getChildren()) { if (child instanceof BlockNode) { @@ -51,7 +58,8 @@ public class ExtendNodeHandler { } else { for (Entry<String, List<ArrayList<String>>> entry : extendsMap .entrySet()) { - if (selectorString.contains(entry.getKey())) { + if (StringUtil.containsSubString(selectorString, + entry.getKey())) { for (ArrayList<String> sList : entry.getValue()) { ArrayList<String> clone = (ArrayList<String>) sList .clone(); @@ -71,22 +79,36 @@ public class ExtendNodeHandler { if (extendsMap.get(extendedString) == null) { extendsMap.put(extendedString, new ArrayList<ArrayList<String>>()); } - extendsMap.get(extendedString).add( - ((BlockNode) node.getParentNode()).getSelectorList()); + // prevent a selector extends itself, e.g. .test{ @extend .test} + String parentSelectorString = ((BlockNode) node.getParentNode()) + .getSelectors(); + if (!parentSelectorString.equals(extendedString)) { + extendsMap.get(extendedString).add( + ((BlockNode) node.getParentNode()).getSelectorList()); + } } private static void addAdditionalSelectorListToBlockNode( - BlockNode blockNode, ArrayList<String> list, String selectorString) { - if (list != null) { - for (int i = 0; i < list.size(); i++) { - if (selectorString == null) { - blockNode.getSelectorList().add(list.get(i)); + BlockNode blockNode, ArrayList<String> extendingSelectors, + String extendedSelector) { + if (extendingSelectors != null) { + for (String extendingSelector : extendingSelectors) { + if (extendedSelector == null) { + blockNode.getSelectorList().add(extendingSelector); } else { ArrayList<String> newTags = new ArrayList<String>(); - for (final String existing : blockNode.getSelectorList()) { - if (existing.contains(selectorString)) { - newTags.add(existing.replace(selectorString, - list.get(i))); + for (final String selectorString : blockNode + .getSelectorList()) { + if (StringUtil.containsSubString(selectorString, + extendedSelector)) { + String newTag = generateExtendingSelectors( + selectorString, extendedSelector, + extendingSelector); + // prevent adding duplicated selector list + if (!blockNode.getSelectorList().contains(newTag) + && !newTags.contains(newTag)) { + newTags.add(newTag); + } } } blockNode.getSelectorList().addAll(newTags); @@ -94,4 +116,15 @@ public class ExtendNodeHandler { } } } + + private static String generateExtendingSelectors(String selectorString, + String extendedSelector, String extendingSelector) { + String result = StringUtil.replaceSubString(selectorString, + extendedSelector, extendingSelector); + // remove duplicated class selectors. + if (result.startsWith(".")) { + result = StringUtil.removeDuplicatedSubString(result, "."); + } + return result; + } } diff --git a/theme-compiler/tests/resources/automatic/css/extend-in-nested-block.css b/theme-compiler/tests/resources/automatic/css/extend-in-nested-block.css new file mode 100644 index 0000000000..29f1550dd7 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/css/extend-in-nested-block.css @@ -0,0 +1,7 @@ +.test .error, .test .seriousError { + border: 1px #f00; + background-color: #fdd; +} +.test .seriousError { + border-width: 3px; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/css/extend-selector-in-different-levels.css b/theme-compiler/tests/resources/automatic/css/extend-selector-in-different-levels.css new file mode 100644 index 0000000000..4de05d8d82 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/css/extend-selector-in-different-levels.css @@ -0,0 +1,15 @@ +.test .middle .error, .test .middle .seriousError { + border: 1px #f00; + background-color: #fdd; +} +.test .seriousError { + border-width: 3px; +} + +.test1 .error1, .test1 .middle1 .seriousError1 { + border: 1px #f00; + background-color: #fdd; +} +.test1 .middle1 .seriousError1 { + border-width: 3px; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/css/extending-non-exist-selector-with-same-beginning.css b/theme-compiler/tests/resources/automatic/css/extending-non-exist-selector-with-same-beginning.css new file mode 100644 index 0000000000..d138a79e4a --- /dev/null +++ b/theme-compiler/tests/resources/automatic/css/extending-non-exist-selector-with-same-beginning.css @@ -0,0 +1,7 @@ +.test1 { + color: blue; +} + +.test2 { + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/css/extending-same-selector.css b/theme-compiler/tests/resources/automatic/css/extending-same-selector.css new file mode 100644 index 0000000000..1a85c0c23e --- /dev/null +++ b/theme-compiler/tests/resources/automatic/css/extending-same-selector.css @@ -0,0 +1,7 @@ +.test { + color: blue; +} + +.test { + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/css/extending-selector-with-same-beginning.css b/theme-compiler/tests/resources/automatic/css/extending-selector-with-same-beginning.css new file mode 100644 index 0000000000..097d7a8655 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/css/extending-selector-with-same-beginning.css @@ -0,0 +1,7 @@ +.test1, .test2 { + color: blue; +} + +.test2 { + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/scss/extend-in-nested-block.scss b/theme-compiler/tests/resources/automatic/scss/extend-in-nested-block.scss new file mode 100644 index 0000000000..d62ead937e --- /dev/null +++ b/theme-compiler/tests/resources/automatic/scss/extend-in-nested-block.scss @@ -0,0 +1,11 @@ +.test{ + .error { + border: 1px #f00; + background-color: #fdd; + } + + .seriousError { + @extend .error; + border-width: 3px; + } +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/scss/extend-selector-in-different-levels.scss b/theme-compiler/tests/resources/automatic/scss/extend-selector-in-different-levels.scss new file mode 100644 index 0000000000..977ead8d62 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/scss/extend-selector-in-different-levels.scss @@ -0,0 +1,26 @@ +.test{ + .middle{ + .error { + border: 1px #f00; + background-color: #fdd; + } + } + + .seriousError { + @extend .error; + border-width: 3px; + } +} + +.test1{ + .error1 { + border: 1px #f00; + background-color: #fdd; + } + .middle1{ + .seriousError1 { + @extend .error1; + border-width: 3px; + } + } +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/scss/extending-non-exist-selector-with-same-beginning.scss b/theme-compiler/tests/resources/automatic/scss/extending-non-exist-selector-with-same-beginning.scss new file mode 100644 index 0000000000..538f17da1d --- /dev/null +++ b/theme-compiler/tests/resources/automatic/scss/extending-non-exist-selector-with-same-beginning.scss @@ -0,0 +1,8 @@ +.test1 { + color: blue; +} + +.test2 { + @extend .test; + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/scss/extending-same-selector.scss b/theme-compiler/tests/resources/automatic/scss/extending-same-selector.scss new file mode 100644 index 0000000000..fbfaed9b20 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/scss/extending-same-selector.scss @@ -0,0 +1,8 @@ +.test { + color: blue; +} + +.test { + @extend .test; + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/automatic/scss/extending-selector-with-same-beginning.scss b/theme-compiler/tests/resources/automatic/scss/extending-selector-with-same-beginning.scss new file mode 100644 index 0000000000..c7a9e5e921 --- /dev/null +++ b/theme-compiler/tests/resources/automatic/scss/extending-selector-with-same-beginning.scss @@ -0,0 +1,8 @@ +.test1 { + color: blue; +} + +.test2 { + @extend .test1; + background: red; +}
\ No newline at end of file diff --git a/theme-compiler/tests/resources/sasslangbroken/css/19-test_control_flow_if.css b/theme-compiler/tests/resources/sasslang/css/19-test_control_flow_if.css index 14a1c6ef3c..14a1c6ef3c 100644 --- a/theme-compiler/tests/resources/sasslangbroken/css/19-test_control_flow_if.css +++ b/theme-compiler/tests/resources/sasslang/css/19-test_control_flow_if.css diff --git a/theme-compiler/tests/resources/sasslangbroken/scss/19-test_control_flow_if.scss b/theme-compiler/tests/resources/sasslang/scss/19-test_control_flow_if.scss index be53e52341..be53e52341 100644 --- a/theme-compiler/tests/resources/sasslangbroken/scss/19-test_control_flow_if.scss +++ b/theme-compiler/tests/resources/sasslang/scss/19-test_control_flow_if.scss diff --git a/theme-compiler/tests/src/com/vaadin/sass/internal/util/StringUtilTest.java b/theme-compiler/tests/src/com/vaadin/sass/internal/util/StringUtilTest.java index b05b0e9dcf..84d189d8ba 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/internal/util/StringUtilTest.java +++ b/theme-compiler/tests/src/com/vaadin/sass/internal/util/StringUtilTest.java @@ -50,4 +50,62 @@ public class StringUtilTest { Assert.assertEquals(sentence, StringUtil.replaceVariable(sentence, word, value)); } + + @Test + public void testContainsSubString() { + String sentence = "var1 var2"; + String word = "var"; + Assert.assertFalse(StringUtil.containsSubString(sentence, word)); + + word = "var1"; + Assert.assertTrue(StringUtil.containsSubString(sentence, word)); + + String var2 = "var2"; + Assert.assertTrue(StringUtil.containsSubString(sentence, var2)); + + Assert.assertTrue(StringUtil.containsSubString(".error.intrusion", + ".error")); + + Assert.assertFalse(StringUtil.containsSubString(".foo", "oo")); + } + + @Test + public void testContainsSubStringWithDash() { + String sentence = "var- var2"; + String word = "var"; + Assert.assertFalse(StringUtil.containsSubString(sentence, word)); + } + + @Test + public void testReplaceSubString() { + String sentence = "var1 var2"; + String word = "var"; + String value = "abc"; + + word = "var1"; + Assert.assertEquals("abc var2", + StringUtil.replaceSubString(sentence, word, value)); + + String var2 = "var1 abc"; + Assert.assertEquals(sentence, + StringUtil.replaceSubString(sentence, var2, value)); + + Assert.assertEquals(".foo", + StringUtil.replaceSubString(".foo", "oo", "aa")); + } + + @Test + public void testReplaceSubStringWithDash() { + String sentence = "var- var2"; + String word = "var"; + String value = "abc"; + Assert.assertEquals(sentence, + StringUtil.replaceSubString(sentence, word, value)); + } + + @Test + public void testRemoveDuplicatedClassSelector() { + Assert.assertEquals(".seriousError", StringUtil + .removeDuplicatedSubString(".seriousError.seriousError", ".")); + } } diff --git a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/AbstractDirectoryScanningSassTests.java b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/AbstractDirectoryScanningSassTests.java index f990647978..47657f805c 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/AbstractDirectoryScanningSassTests.java +++ b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/AbstractDirectoryScanningSassTests.java @@ -27,15 +27,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import junit.framework.TestCase; - import org.apache.commons.io.IOUtils; import org.junit.Assert; import com.vaadin.sass.internal.ScssStylesheet; import com.vaadin.sass.testcases.scss.SassTestRunner.FactoryTest; -public abstract class AbstractDirectoryScanningSassTests extends TestCase { +public abstract class AbstractDirectoryScanningSassTests { public static Collection<String> getScssResourceNames(URL directoryUrl) throws URISyntaxException { @@ -51,7 +49,7 @@ public abstract class AbstractDirectoryScanningSassTests extends TestCase { URL sasslangUrl = directoryUrl; File sasslangDir = new File(sasslangUrl.toURI()); File scssDir = new File(sasslangDir, "scss"); - assertTrue(scssDir.exists()); + Assert.assertTrue(scssDir.exists()); return scssDir.listFiles(new FilenameFilter() { diff --git a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTests.java b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTests.java index 7f42898fe5..4b8aada524 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTests.java +++ b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTests.java @@ -32,7 +32,12 @@ public class SassLangTests extends AbstractDirectoryScanningSassTests { } private static URL getResourceURLInternal(String path) { - return SassLangTests.class.getResource("/sasslang" + path); + URL url = SassLangTests.class.getResource("/sasslang" + path); + if (url == null) { + throw new RuntimeException( + "Could not locate /sasslang using classloader"); + } + return url; } @TestFactory diff --git a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTestsBroken.java b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTestsBroken.java index 897e8dc543..6b812a6940 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTestsBroken.java +++ b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassLangTestsBroken.java @@ -19,7 +19,9 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; +import org.junit.Assert; import org.junit.runner.RunWith; +import org.w3c.css.sac.CSSException; import com.vaadin.sass.testcases.scss.SassTestRunner.TestFactory; @@ -32,7 +34,13 @@ public class SassLangTestsBroken extends AbstractDirectoryScanningSassTests { } private static URL getResourceURLInternal(String path) { - return SassLangTestsBroken.class.getResource("/sasslangbroken" + path); + URL url = SassLangTestsBroken.class.getResource("/sasslangbroken" + + path); + if (url == null) { + throw new RuntimeException( + "Could not locate /sasslangbroken using classloader"); + } + return url; } @TestFactory @@ -41,4 +49,22 @@ public class SassLangTestsBroken extends AbstractDirectoryScanningSassTests { return getScssResourceNames(getResourceURLInternal("")); } + @Override + public void compareScssWithCss(String scssResourceName) throws Exception { + boolean success = false; + try { + super.compareScssWithCss(scssResourceName); + success = true; + } catch (CSSException e) { + // this is an expected outcome + } catch (AssertionError e) { + // this is an expected outcome + } + if (success) { + Assert.fail("Test " + + scssResourceName + + " from sasslangbroken that was expected to fail has been fixed. Please move the corresponding CSS and SCSS files to sasslang."); + } + } + } diff --git a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassTestRunner.java b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassTestRunner.java index da5210b2da..147362e4c7 100644 --- a/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassTestRunner.java +++ b/theme-compiler/tests/src/com/vaadin/sass/testcases/scss/SassTestRunner.java @@ -80,7 +80,7 @@ public class SassTestRunner extends BlockJUnit4ClassRunner { getTestClass().getJavaClass()); } catch (Throwable t) { throw new RuntimeException("Could not run test factory method " - + method.getName()); + + method.getName(), t); } // Did the factory return an array? If so, make it a list. diff --git a/uitest/src/com/vaadin/tests/widgetset/client/DelegateWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/DelegateWidget.java index 0543bdf073..8b71e2bdec 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/DelegateWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/DelegateWidget.java @@ -41,6 +41,7 @@ public class DelegateWidget extends HTML { public void setValue4(double value4) { this.value4 = value4; + updateText(); } private void updateText() { |