http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/SequenceMapAdapter.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SequenceMapAdapter.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/SequenceMapAdapter.java deleted file mode 100644 index e1812c1..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SequenceMapAdapter.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import org.waveprotocol.wave.model.util.Preconditions; - -/** - * Presents a sequence that is backed by a {@link SequenceMap}. - * - * In particular, this class removes the inconvenient parts of the - * {@link SequenceMap} API (sequence elements are exposed, circularity, - * map-ness), presenting a nice, simple, sequence. - * - */ -public final class SequenceMapAdapter<T extends VolatileComparable<T>> implements - Sequence<T> { - - /** Underlying doodad map. */ - private final SequenceMap<T, T> map; - - private SequenceMapAdapter(SequenceMap<T, T> map) { - this.map = map; - } - - public static <T extends VolatileComparable<T>> SequenceMapAdapter<T> create( - SequenceMap<T, T> maps) { - return new SequenceMapAdapter<T>(maps); - } - - @Override - public T getFirst() { - SequenceElement<T> first = map.getFirst(); - return first != null ? first.value() : null; - } - - @Override - public T getLast() { - SequenceElement<T> last = map.getLast(); - return last != null ? last.value() : null; - } - - @Override - public T getNext(T item) { - SequenceElement<T> node = map.getElement(item); - Preconditions.checkArgument(node != null, "item not in this sequence"); - return node != map.getLast() ? node.getNext().value() : null; - } - - @Override - public T getPrevious(T item) { - SequenceElement<T> node = map.getElement(item); - Preconditions.checkArgument(node != null, "item not in this sequence"); - return node != map.getFirst() ? node.getPrev().value() : null; - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public boolean contains(T x) { - return x != null && map.get(x) != null; - } - - public void add(T x) { - map.put(x, x); - } - - public void remove(T x) { - map.remove(x); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEvent.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEvent.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEvent.java deleted file mode 100644 index 91dff61..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEvent.java +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.Event; - -/** - * Attempts to bring sanity to the incredibly complex and inconsistent world of - * browser events, especially with regards to key events. - * - * {@link SignalEventImpl} - * - * @author danila...@google.com (Daniel Danilatos) - */ -public interface SignalEvent { - - /** - * The "type" of key signal. The type is defined by us, based on a convenient - * classification of these events. - */ - public enum KeySignalType { - /** - * Typing in the form of inputting text - * - * NOTE: "TAB" is treated as INPUT. - */ - INPUT, - /** Moving around */ - NAVIGATION, - /** Deleting text (Backspace + Delete) */ - DELETE, - /** Other, like ESC or F3, that by themselves will do nothing to the document */ - NOEFFECT, - /** Sentinal for debugging purposes only */ - SENTINAL - } - - /** - * The "movement unit" of this event. For example, on windows and linux, - * holding down CTRL whilst navigating left and right with the arrow - * keys moves a word at a time. Even more importantly, move units - * are used for deleting text. - */ - public enum MoveUnit { - /** A character at a time */ - CHARACTER, - /** A word at a time */ - WORD, - /** To the start or end of the line */ - LINE, - /** A "page" at a time */ - PAGE, - /** To the start or end of the document */ - DOCUMENT - } - - /** - * These are the currently supported modifier combinations - * - * Supporting more requires tricky, browser-specific code. Go to the experiment harness - * and have a look at the wonders.... - */ - public enum KeyModifier { - /** No modifiers are pressed */ - NONE { - @Override - public boolean check(SignalEvent event) { - return !event.getShiftKey() && !event.getAltKey() - && !event.getCtrlKey() && !event.getMetaKey(); - } - }, - /** Only shift is pressed */ - SHIFT { - @Override - public boolean check(SignalEvent event) { - return event.getShiftKey() && !event.getAltKey() - && !event.getCtrlKey() && !event.getMetaKey(); - } - }, - /** Only ctrl is pressed */ - CTRL { - @Override - public boolean check(SignalEvent event) { - return !event.getShiftKey() && !event.getAltKey() - && event.getCtrlKey() && !event.getMetaKey(); - } - }, - /** Only alt is pressed */ - ALT { - @Override - public boolean check(SignalEvent event) { - return !event.getShiftKey() && event.getAltKey() - && !event.getCtrlKey() && !event.getMetaKey(); - } - }, - /** Only the meta key is pressed */ - META { - @Override - public boolean check(SignalEvent event) { - return !event.getShiftKey() && !event.getAltKey() - && !event.getCtrlKey() && event.getMetaKey(); - } - }, - /** - * Only the "command" key is pressed - * @see SignalEvent#COMMAND_IS_CTRL - */ - COMMAND { - @Override - public boolean check(SignalEvent event) { - return COMMAND_IS_CTRL ? CTRL.check(event) : META.check(event); - } - }, - /** Both ctrl and alt, but no others, are pressed */ - CTRL_ALT { - @Override - public boolean check(SignalEvent event) { - return !event.getShiftKey() && event.getAltKey() - && event.getCtrlKey() && !event.getMetaKey(); - } - }, - /** Both "command" and shift, but no others, are pressed */ - COMMAND_SHIFT { - @Override - public boolean check(SignalEvent event) { - // The ctrl/meta key which is NOT the command key - boolean notCommandKey = COMMAND_IS_CTRL ? event.getMetaKey() : event.getCtrlKey(); - return event.getShiftKey() && !event.getAltKey() - && event.getCommandKey() && !notCommandKey; - } - } - ; - - /** - * Whether the "command" key is the control key (as opposed to the meta key). - */ - // TODO(danilatos): Reconcile this with the value in SignalKeyLogic. - private static final boolean COMMAND_IS_CTRL = !UserAgent.isMac(); - - /** - * Check if the given event has the enum value's modifiers pressed. - * @param event - * @return true if they are pressed - */ - public abstract boolean check(SignalEvent event); - } - - - /** - * @return Event type as a string, e.g. "keypress" - */ - String getType(); - - /** - * @return The target element of the event - */ - Element getTarget(); - - /** - * @return true if the event is a key event - * TODO(danilatos): Have a top level EventSignalType enum - */ - boolean isKeyEvent(); - - /** - * @return true if it is an IME composition event - */ - boolean isCompositionEvent(); - - /** - * Returns true if the key event is an IME input event. - * Only makes sense to call this method if this is a key signal. - * Does not work on FF. (TODO(danilatos): Can it be done? Tricks - * with dom mutation events?) - * - * @return true if this is an IME input event - */ - boolean isImeKeyEvent(); - - /** - * @return true if this is a mouse event - * TODO(danilatos): Have a top level EventSignalType enum - */ - boolean isMouseEvent(); - - /** - * TODO(danilatos): Click + drag? I.e. return true for mouse move, if the - * button is pressed? (this might be useful for tracking changing selections - * as the user holds & drags) - * @return true if this is an event involving some use of mouse buttons - */ - boolean isMouseButtonEvent(); - - /** - * @return true if this is a mouse event but not {@link #isMouseButtonEvent()} - */ - boolean isMouseButtonlessEvent(); - - /** - * @return true if this is a "click" event - */ - boolean isClickEvent(); - - /** - * @return True if this is a dom mutation event - */ - boolean isMutationEvent(); - - /** - * @return true if this is any sort of clipboard event - */ - boolean isClipboardEvent(); - - /** - * @return If this is a focus event - */ - boolean isFocusEvent(); - - /** - * @return true if this is a paste event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - boolean isPasteEvent(); - - /** - * @return true if this is a cut event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - boolean isCutEvent(); - - /** - * @return true if this is a copy event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - boolean isCopyEvent(); - - /** - * @return true if the command key is depressed - * @see #COMMAND_IS_CTRL - */ - boolean getCommandKey(); - - /** - * @return true if the ctrl key is depressed - */ - boolean getCtrlKey(); - - /** - * @return true if the meta key is depressed - */ - boolean getMetaKey(); - - /** - * @return true if the alt key is depressed - */ - boolean getAltKey(); - - /** - * @return true if the shift key is depressed - */ - boolean getShiftKey(); - - /** - * TODO(user): Deprecate this, as it breaks abstraction. Also prevent people - * from casting back and forth. - * - * @return The underlying event view of this event - */ - Event asEvent(); - - /** - * Only valid for key events. - * Currently only implemented for deleting, not actual navigating. - * @return The move unit of this event - */ - MoveUnit getMoveUnit(); - - /** - * @return True if the event is the key combo for "undo" - * NOTE(danilatos): This is the best we have at detecting undo events :( - * We need even more special handling for undo done via the menu :( :( - */ - boolean isUndoCombo(); - - /** - * @return True if the event is the key combo for "redo" - */ - boolean isRedoCombo(); - - /** - * @param letter Treated case-insensitive, including things like '1' vs '!' - * User may provide either, but upper case for letters and unshifted for - * other keys is recommended - * @param modifier - * @return True if the given letter is pressed, and only the given modifiers. - */ - boolean isCombo(int letter, KeyModifier modifier); - - /** - * @param letter - * @return true, if the given letter was pressed without modifiers. Takes into - * account the caps lock key being pressed (it will be as if it - * weren't pressed) - */ - boolean isOnly(int letter); - - /** - * @return The gwtKeyCode of this event, with some minor compatibility - * adjustments - */ - int getKeyCode(); - - /** - * @return the mouse button bit field for this event. The masks used to extract - * individual buttons from the field are {@link NativeEvent#BUTTON_LEFT}, - * {@link NativeEvent#BUTTON_MIDDLE}, and {@link NativeEvent#BUTTON_RIGHT}. - */ - int getMouseButton(); - - /** - * @return The key signal type of this even, or null if it is not a key event - * @see KeySignalType - */ - KeySignalType getKeySignalType(); - - /** - * Prevent this event from propagating or, if that is impossible, ensures that - * {@link SignalEventImpl#create(Event, boolean)} will subsequently return null - * for the same corresponding event object. - */ - void stopPropagation(); - - /** - * Prevents the browser from taking its default action for the given event. - * Under some circumstances (see {@link SignalEventImpl#isPreventDefaultEffective} - * this may also stop propagation. - * - * See also {@link #stopPropagation()}. - */ - public void preventDefault(); -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEventImpl.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEventImpl.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEventImpl.java deleted file mode 100644 index 5d70221..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalEventImpl.java +++ /dev/null @@ -1,668 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.Event; - -import org.waveprotocol.wave.client.common.util.SignalKeyLogic.OperatingSystem; -import org.waveprotocol.wave.client.common.util.SignalKeyLogic.UserAgentType; -import org.waveprotocol.wave.model.util.CollectionUtils; -import org.waveprotocol.wave.model.util.StringSet; - -/** - * Attempts to bring sanity to the incredibly complex and inconsistent world of - * browser events, especially with regards to key events. - * - * A new concept of the "signal" is introduced. A signal is basically an event, - * but an event that we actually care about, with the information we care about. - * Redundant events are merged into a single signal. For key events, a signal - * corresponds to the key-repeat signal we get from the keyboard. For normal - * typing input, this will always be the keypress event. For other types of key - * events, it depends on the browser. For clipboard events, the "beforeXYZ" and - * "XYZ" events are merged into a single one, the one that actually happens - * right before the action (browser dependent). Key events are also classified - * into subtypes identified by KeySignalType. This reflects the intended usage - * of the event, not something to do with the event data itself. - * - * Currently the "filtering" needs to be done manually - simply construct a - * signal from an event using {@link #create(Event, boolean)}, and if it returns null, - * drop the event and do nothing with it (cancelling bubbling might be a good - * idea though). - * - * NOTE(danilatos): getting the physical key pressed, even on a key down, is - * inherently not possible without a big lookup table, because of international - * input methods. e.g. press 'b' but in greek mode on safari on osx. nothing in - * any of the events you receive will tell you it was a 'b', instead, you'll get - * a beta for the keypress and 0 (zero) for the keydown. mmm, useful! - * - * TODO(danilatos): Hook this into the application's event plumbing in a more - * invasive manner. - * - * @author danila...@google.com (Daniel Danilatos) - */ -public class SignalEventImpl implements SignalEvent { - - public interface SignalEventFactory<T extends SignalEventImpl> { - T create(); - } - - public static SignalEventFactory<SignalEventImpl> DEFAULT_FACTORY = - new SignalEventFactory<SignalEventImpl>() { - @Override public SignalEventImpl create() { - return new SignalEventImpl(); - } - }; - - interface NativeEvent { - String getType(); - int getButton(); - boolean getCtrlKey(); - boolean getMetaKey(); - boolean getAltKey(); - boolean getShiftKey(); - void preventDefault(); - void stopPropagation(); - } - - /** - * @param event - * @return True if the given event is a key event - */ - public static boolean isKeyEvent(Event event) { - return KEY_EVENTS.contains(event.getType()); - } - - private static final UserAgentType currentUserAgent = - (UserAgent.isWebkit() ? UserAgentType.WEBKIT : ( - UserAgent.isFirefox() ? UserAgentType.GECKO : UserAgentType.IE)); - private static final OperatingSystem currentOs = - (UserAgent.isWin() ? OperatingSystem.WINDOWS : ( - UserAgent.isMac() ? OperatingSystem.MAC : OperatingSystem.LINUX)); - private static final SignalKeyLogic logic = new SignalKeyLogic( - currentUserAgent, currentOs, QuirksConstants.COMMAND_COMBO_DOESNT_GIVE_KEYPRESS); - - /** - * This variable will be filled with mappings of unshifted keys to their shifted versions. - */ - private static final int[] shiftMappings = new int[128]; - static { - for (int a = 'A'; a <= 'Z'; a++) { - shiftMappings[a] = a + 'a' - 'A'; - } - // TODO(danilatos): Who knows what these mappings should be on other - // keyboard layouts... e.g. pound signs? euros? etc? argh! - shiftMappings['1'] = '!'; - shiftMappings['2'] = '@'; - shiftMappings['3'] = '#'; - shiftMappings['4'] = '$'; - shiftMappings['5'] = '%'; - shiftMappings['6'] = '^'; - shiftMappings['7'] = '&'; - shiftMappings['8'] = '*'; - shiftMappings['9'] = '('; - shiftMappings['0'] = ')'; - shiftMappings['`'] = '~'; - shiftMappings['-'] = '_'; - shiftMappings['='] = '+'; - shiftMappings['['] = '{'; - shiftMappings[']'] = '}'; - shiftMappings['\\'] = '|'; - shiftMappings[';'] = ':'; - shiftMappings['\''] = '"'; - shiftMappings[','] = '<'; - shiftMappings['.'] = '>'; - shiftMappings['/'] = '?'; - // invalidate the inverse mappings - for (int i = 1; i < shiftMappings.length; i++) { - int m = shiftMappings[i]; - if (m > 0) { - shiftMappings[m] = i; - } - } - } - - private static final StringSet KEY_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet COMPOSITION_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet MOUSE_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet MOUSE_BUTTON_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet MOUSE_BUTTONLESS_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet FOCUS_EVENTS = CollectionUtils.createStringSet(); - private static final StringSet CLIPBOARD_EVENTS = CollectionUtils.createStringSet(); - - /** - * Events affected by - * {@link QuirksConstants#CANCEL_BUBBLING_CANCELS_IME_COMPOSITION_AND_CONTEXTMENU}. - */ - private static final StringSet CANCEL_BUBBLE_QUIRKS = CollectionUtils.createStringSet(); - - static { - for (String e : new String[]{"keydown", "keypress", "keyup"}) { - KEY_EVENTS.add(e); - } - for (String e : new String[]{ - "compositionstart", "compositionend", "compositionupdate", "text"}) { - COMPOSITION_EVENTS.add(e); - CANCEL_BUBBLE_QUIRKS.add(e); - } - COMPOSITION_EVENTS.add("textInput"); - CANCEL_BUBBLE_QUIRKS.add("contextmenu"); - for (String e : new String[]{ - "mousewheel", "DOMMouseScroll", "mousemove", "mouseover", "mouseout", - /* not strictly a mouse event*/ "contextmenu"}) { - MOUSE_BUTTONLESS_EVENTS.add(e); - MOUSE_EVENTS.add(e); - } - for (String e : new String[]{ - "mousedown", "mouseup", "click", "dblclick"}) { - MOUSE_BUTTON_EVENTS.add(e); - MOUSE_EVENTS.add(e); - } - for (String e : new String[]{"focus", "blur", "beforeeditfocus"}) { - FOCUS_EVENTS.add(e); - } - for (String e : new String[]{"cut", "copy", "paste"}) { - CLIPBOARD_EVENTS.add(e); - CLIPBOARD_EVENTS.add("before" + e); - } - } - - protected NativeEvent nativeEvent; - private KeySignalType keySignalType = null; - private int cachedKeyCode = -1; - private boolean hasBeenConsumed = false; - - protected SignalEventImpl() { - } - - static class JsoNativeEvent extends Event implements NativeEvent { - protected JsoNativeEvent() {} - } - - /** - * Create a signal from an event, possibly filtering the event - * if it is deemed redundant. - * - * If the event is to be filtered, null is returned, and bubbling - * is cancelled if cancelBubbleIfNullified is true. - * (but the default is not prevented). - * - * NOTE(danilatos): So far, for key events, the following have been tested: - * - Safari 3.1 OS/X (incl. num pad, with USB keyboard) - * - Safari 3.0 OS/X, hosted mode only (so no ctrl+c, etc) - * - Firefox 3, OS/X, WinXP - * - IE7, WinXP - * Needs testing: - * - FF3 linux, Safari 3.0/3.1 Windows - * - All kinds of weirdo keyboards (mac, international) - * - Linux IME - * - * Currently, only key events have serious logic applied to them. - * Maybe some logic for copy/paste, and mouse events? - * - * @param event Raw Event JSO - * @param cancelBubbleIfNullified stops propagation if the event is nullified - * @return SignalEvent mapping, or null, if the event is to be discarded - */ - public static SignalEventImpl create(Event event, boolean cancelBubbleIfNullified) { - return create(DEFAULT_FACTORY, event, cancelBubbleIfNullified); - } - - public static <T extends SignalEventImpl> T create(SignalEventFactory<T> factory, - Event event, boolean cancelBubbleIfNullified) { - if (hasBeenConsumed(event)) { - return null; - } else { - T signal = createInner(factory, event); - if (cancelBubbleIfNullified && signal == null) { - event.stopPropagation(); - } - return signal; - } - } - - private static boolean hasBeenConsumed(Event event) { - SignalEventImpl existing = getFor(null, event); - return existing != null && existing.hasBeenConsumed(); - } - - private static final String EVENT_PROP = "$se"; - @SuppressWarnings("unchecked") - private static <T extends SignalEventImpl> T getFor(SignalEventFactory<T> factory, Event event) { - return (T) (SignalEventImpl) event.<JsoView>cast().getObject(EVENT_PROP); - } - - private static <T extends SignalEventImpl> T createFor( - SignalEventFactory<T> factory, Event event) { - - T signal = factory.create(); - event.<JsoView>cast().setObject(EVENT_PROP, signal); - return signal; - } - - /** This would be a static local variable if java allowed it. Grouping it here. */ - private static final SignalKeyLogic.Result computeKeySignalTypeResult = - new SignalKeyLogic.Result(); - - private static <T extends SignalEventImpl> T createInner( - SignalEventFactory<T> factory, Event event) { - - SignalKeyLogic.Result keySignalResult; - if (isKeyEvent(event)) { - keySignalResult = computeKeySignalTypeResult; - - String keyIdentifier = getKeyIdentifier(event); - - logic.computeKeySignalType(keySignalResult, - event.getType(), getNativeKeyCode(event), getWhich(event), keyIdentifier, - event.getMetaKey(), event.getCtrlKey(), event.getAltKey(), event.getShiftKey()); - - } else { - keySignalResult = null; - } - - return createInner(createFor(factory, event), event.<JsoNativeEvent>cast(), keySignalResult); - } - - /** - * Populate a SignalEventImpl with the necessary information - * - * @param ret - * @param keySignalResult only required if it's a key event - * @return the signal, or null if it is to be ignored. - */ - protected static <T extends SignalEventImpl> T createInner(T ret, - NativeEvent event, SignalKeyLogic.Result keySignalResult) { - ret.nativeEvent = event; - if (ret.isKeyEvent()) { - KeySignalType type = keySignalResult.type; - - if (type != null) { - ret.cacheKeyCode(keySignalResult.keyCode); - ret.setup(type); - } else { - ret = null; - } - - } else if ((UserAgent.isIE() ? "paste" : "beforepaste").equals(event.getType())) { - // Only want 'beforepaste' for ie and 'paste' for everything else. - // TODO(danilatos): Generalise clipboard events - ret = null; - } - - // TODO: return null if it's something we should ignore. - return ret; - } - - public static native int getNativeKeyCode(Event event) /*-{ - return event.keyCode || 0; - }-*/; - - public static native int getWhich(Event event) /*-{ - return event.which || 0; - }-*/; - - public static native String getKeyIdentifier(Event event) /*-{ - return event.keyIdentifier - }-*/; - - /** - * @return Event type as a string, e.g. "keypress" - */ - public final String getType() { - return nativeEvent.getType(); - } - - /** - * @return The target element of the event - */ - public Element getTarget() { - return asEvent().getTarget(); - } - - /** - * @return true if the event is a key event - * TODO(danilatos): Have a top level EventSignalType enum - */ - public final boolean isKeyEvent() { - return KEY_EVENTS.contains(nativeEvent.getType()); - } - - /** - * @return true if it is an IME composition event - */ - public final boolean isCompositionEvent() { - return COMPOSITION_EVENTS.contains(getType()); - } - - /** - * Returns true if the key event is an IME input event. - * Only makes sense to call this method if this is a key signal. - * Does not work on FF. (TODO(danilatos): Can it be done? Tricks - * with dom mutation events?) - * - * @return true if this is an IME input event - */ - public final boolean isImeKeyEvent() { - return getKeyCode() == SignalKeyLogic.IME_CODE; - } - - /** - * @return true if this is a mouse event - * TODO(danilatos): Have a top level EventSignalType enum - */ - public final boolean isMouseEvent() { - return MOUSE_EVENTS.contains(getType()); - } - - /** - * TODO(danilatos): Click + drag? I.e. return true for mouse move, if the - * button is pressed? (this might be useful for tracking changing selections - * as the user holds & drags) - * @return true if this is an event involving some use of mouse buttons - */ - public final boolean isMouseButtonEvent() { - return MOUSE_BUTTON_EVENTS.contains(getType()); - } - - /** - * @return true if this is a mouse event but not {@link #isMouseButtonEvent()} - */ - public final boolean isMouseButtonlessEvent() { - return MOUSE_BUTTONLESS_EVENTS.contains(getType()); - } - - /** - * @return true if this is a "click" event - */ - public final boolean isClickEvent() { - return "click".equals(getType()); - } - - /** - * @return True if this is a dom mutation event - */ - public final boolean isMutationEvent() { - // What about DOMMouseScroll? - return getType().startsWith("DOM"); - } - - /** - * @return true if this is any sort of clipboard event - */ - public final boolean isClipboardEvent() { - return CLIPBOARD_EVENTS.contains(getType()); - } - - /** - * @return If this is a focus event - */ - public final boolean isFocusEvent() { - return FOCUS_EVENTS.contains(getType()); - } - - /** - * @return true if this is a paste event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - public final boolean isPasteEvent() { - return (UserAgent.isIE() ? "beforepaste" : "paste").equals(nativeEvent.getType()); - } - - /** - * @return true if this is a cut event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - public final boolean isCutEvent() { - return (UserAgent.isIE() ? "beforecut" : "cut").equals(nativeEvent.getType()); - } - - /** - * @return true if this is a copy event - * TODO(danilatos): Make a ClipboardSignalType enum instead - */ - public final boolean isCopyEvent() { - return "copy".equals(nativeEvent.getType()); - } - - /** - * @return true if the command key is depressed - * @see SignalKeyLogic#commandIsCtrl() - */ - public final boolean getCommandKey() { - return logic.commandIsCtrl() ? getCtrlKey() : getMetaKey(); - } - - public static boolean getCommandKey(com.google.gwt.dom.client.NativeEvent event) { - return logic.commandIsCtrl() ? event.getCtrlKey() : event.getMetaKey(); - } - - /** - * @return true if the ctrl key is depressed - */ - public final boolean getCtrlKey() { - return nativeEvent.getCtrlKey(); - } - - /** - * @return true if the meta key is depressed - */ - public final boolean getMetaKey() { - return nativeEvent.getMetaKey(); - } - - /** - * @return true if the alt key is depressed - */ - public final boolean getAltKey() { - // TODO(danilatos): Handle Alt vs Option on OSX? - return nativeEvent.getAltKey(); - } - - /** - * @return true if the shift key is depressed - */ - public final boolean getShiftKey() { - return nativeEvent.getShiftKey(); - } - - /** - * @return The underlying event view of this event - */ - public final Event asEvent() { - return (Event) nativeEvent; - } - - /** - * Only valid for key events. - * Currently only implemented for deleting, not actual navigating. - * @return The move unit of this event - */ - public final MoveUnit getMoveUnit() { - if (getKeySignalType() == KeySignalType.DELETE) { - if (UserAgent.isMac()) { - if (getAltKey()) { - // Note: in practice, some combinations of bkspc/delete + modifier key - // have no effect. This is inconsistent across browsers. It's probably - // ok to normalise it here, as we will be manually implementing everything - // except character-sized deletes on collapsed selections, and so users - // would get a more consistent (and logical and symmetrical) experience. - return MoveUnit.WORD; - } else if (getCommandKey()) { - return MoveUnit.LINE; - } else { - return MoveUnit.CHARACTER; - } - } else { - if (getCommandKey()) { - return MoveUnit.WORD; - } else { - return MoveUnit.CHARACTER; - } - } - } else { - // TODO(danilatos): Also implement for mere navigation events? - // Currently just for deleting... so we'll at least for now just pretend - // everything else is of character magnitude. This is because we - // probably won't be using the information anyway, instead letting - // the browser just do its default navigation behaviour. - - return MoveUnit.CHARACTER; - } - } - - @Override - public final boolean isUndoCombo() { - return isCombo('Z', KeyModifier.COMMAND); - } - - @Override - public final boolean isRedoCombo() { - if ((UserAgent.isMac() || UserAgent.isLinux()) && - isCombo('Z', KeyModifier.COMMAND_SHIFT)) { - // Mac and Linux accept command-shift-z for undo - return true; - } - // NOTE(user): COMMAND + Y for redo, except for Mac OS X (for chrome, - // default behaviour is browser history) - return !UserAgent.isMac() && isCombo('Y', KeyModifier.COMMAND); - } - - /** - * Because we must use keypress events for FF, in order to get repeats, - * but prefer keydowns for combo type events for the other browsers, - * we need to convert the case here. - * - * @param letter - */ - private final int comboInputKeyCode(char letter) { - // TODO(danilatos): Check the compiled javascript to make sure it does simple - // numerical operations and not string manipulations and conversions... char is - // used all over this file - return UserAgent.isFirefox() - ? letter + 'a' - 'A' - : letter; - } - - /** - * @param letter Treated case-insensitive, including things like '1' vs '!' - * User may provide either, but upper case for letters and unshifted for - * other keys is recommended - * @param modifier - * @return True if the given letter is pressed, and only the given modifiers. - */ - public final boolean isCombo(int letter, KeyModifier modifier) { - assert letter > 0 && letter < shiftMappings.length; - int keyCode = getKeyCode(); - if (keyCode >= shiftMappings.length) { - return false; - } - - return (letter == keyCode || letter == shiftMappings[keyCode]) && modifier.check(this); - } - - /** - * @param letter - * @return true, if the given letter was pressed without modifiers. Takes into - * account the caps lock key being pressed (it will be as if it - * weren't pressed) - */ - public final boolean isOnly(int letter) { - return isCombo(letter, KeyModifier.NONE); - } - - @Override - public final int getMouseButton() { - return nativeEvent.getButton(); - } - - /** - * @return The key signal type of this even, or null if it is not a key event - * @see SignalEvent.KeySignalType - */ - public KeySignalType getKeySignalType() { - return this.keySignalType; - } - - /** - * @return The gwtKeyCode of this event, with some minor compatibility - * adjustments - */ - public int getKeyCode() { - return this.cachedKeyCode; - } - - /** - * Returns true if the event has effectively had its propagation stopped, since - * we couldn't physically stop it due to browser quirkiness. See {@link #stopPropagation()}. - */ - private boolean hasBeenConsumed() { - return hasBeenConsumed; - } - - private void markAsConsumed() { - hasBeenConsumed = true; - } - - protected void cacheKeyCode(int keyCode) { - this.cachedKeyCode = keyCode; - } - - private boolean stopPropagationPreventsDefault() { - if (QuirksConstants.CANCEL_BUBBLING_CANCELS_IME_COMPOSITION_AND_CONTEXTMENU) { - return CANCEL_BUBBLE_QUIRKS.contains(getType()); - } else { - return false; - } - } - - private boolean isPreventDefaultEffective() { - if (QuirksConstants.PREVENT_DEFAULT_STOPS_CONTEXTMENT) { - return true; - } else { - String type = nativeEvent.getType(); - return !type.equals("contextmenu"); - } - } - - @Override - public final void stopPropagation() { - if (stopPropagationPreventsDefault()) { - markAsConsumed(); - } else { - nativeEvent.stopPropagation(); - } - } - - protected final void setup(KeySignalType signalType) { - this.keySignalType = signalType; - } - - @Override - public final void preventDefault() { - nativeEvent.preventDefault(); - if (!isPreventDefaultEffective()) { - // HACK(user): Really we would like the event to continue to propagate - // and stop it immediately before reaching the top, rather than at this - // point. - nativeEvent.stopPropagation(); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalKeyLogic.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalKeyLogic.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalKeyLogic.java deleted file mode 100644 index 5da169a..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SignalKeyLogic.java +++ /dev/null @@ -1,394 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.common.annotations.VisibleForTesting; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.Event; - -import org.waveprotocol.wave.client.common.util.SignalEvent.KeySignalType; -import org.waveprotocol.wave.model.util.CollectionUtils; -import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; -import org.waveprotocol.wave.model.util.StringMap; - -import java.util.HashSet; -import java.util.Set; - -/** - * Instances of this class encapsulate the event to signal mapping logic for a - * specific environment (os/browser). - * - * Contains as much of the signal event logic as possible in a POJO testable - * manner. - * - * @author danila...@google.com (Daniel Danilatos) - */ -public final class SignalKeyLogic { - - /** - * For webkit + IE - * I think also all browsers on windows? - */ - public static final int IME_CODE = 229; - - private static final String DELETE_KEY_IDENTIFIER = "U+007F"; - - //TODO(danilatos): Use int map - private static final Set<Integer> NAVIGATION_KEYS = new HashSet<Integer>(); - private static final StringMap<Integer> NAVIGATION_KEY_IDENTIFIERS = - CollectionUtils.createStringMap(); - static { - - NAVIGATION_KEY_IDENTIFIERS.put("Left", KeyCodes.KEY_LEFT); - NAVIGATION_KEY_IDENTIFIERS.put("Right", KeyCodes.KEY_RIGHT); - NAVIGATION_KEY_IDENTIFIERS.put("Up", KeyCodes.KEY_UP); - NAVIGATION_KEY_IDENTIFIERS.put("Down", KeyCodes.KEY_DOWN); - NAVIGATION_KEY_IDENTIFIERS.put("PageUp", KeyCodes.KEY_PAGEUP); - NAVIGATION_KEY_IDENTIFIERS.put("PageDown", KeyCodes.KEY_PAGEDOWN); - NAVIGATION_KEY_IDENTIFIERS.put("Home", KeyCodes.KEY_HOME); - NAVIGATION_KEY_IDENTIFIERS.put("End", KeyCodes.KEY_END); - - NAVIGATION_KEY_IDENTIFIERS.each(new ProcV<Integer>() { - public void apply(String key, Integer keyCode) { - NAVIGATION_KEYS.add(keyCode); - } - }); - } - - public enum UserAgentType { - WEBKIT, - GECKO, - IE - } - - public enum OperatingSystem { - WINDOWS, - MAC, - LINUX - } - - @VisibleForTesting - public static class Result { - @VisibleForTesting - public int keyCode; - // Sentinal by default for testing purposes - @VisibleForTesting - public KeySignalType type = KeySignalType.SENTINAL; - } - - private final UserAgentType userAgent; - private final boolean commandIsCtrl; - - - // Hack, get rid of this - final boolean commandComboDoesntGiveKeypress; - - /** - * @param userAgent - * @param os Operating system - */ - public SignalKeyLogic(UserAgentType userAgent, OperatingSystem os, - boolean commandComboDoesntGiveKeypress) { - this.userAgent = userAgent; - this.commandComboDoesntGiveKeypress = commandComboDoesntGiveKeypress; - commandIsCtrl = os != OperatingSystem.MAC; - } - - public boolean commandIsCtrl() { - return commandIsCtrl; - } - - public void computeKeySignalType( - Result result, - String typeName, - int keyCode, int which, String keyIdentifier, - boolean metaKey, boolean ctrlKey, boolean altKey, boolean shiftKey) { - - boolean ret = true; - - int typeInt; - if ("keydown".equals(typeName)) { - typeInt = Event.ONKEYDOWN; - } else if ("keypress".equals(typeName)) { - typeInt = Event.ONKEYPRESS; - } else if ("keyup".equals(typeName)) { - result.type = null; - return; - } else { - throw new AssertionError("Non-key-event passed to computeKeySignalType"); - } - - KeySignalType type; - - int computedKeyCode = which != 0 ? which : keyCode; - - if (computedKeyCode == 10) { - computedKeyCode = KeyCodes.KEY_ENTER; - } - - // For non-firefox browsers, we only get keydown events for IME, no keypress - boolean isIME = computedKeyCode == IME_CODE; - - boolean commandKey = commandIsCtrl ? ctrlKey : metaKey; - - switch (userAgent) { - case WEBKIT: - // This is a bit tricky because there are significant differences - // between safari 3.0 and safari 3.1... - - // We could probably actually almost use the same code that we use for IE - // for safari 3.1, because with 3.1 the webkit folks made a big shift to - // get the events to be in line with IE for compatibility. 3.0 events - // are a lot more similar to FF, but different enough to need special - // handling. However, it seems that using more advanced features like - // keyIdentifier for safaris is probably better and more future-proof, - // as well as being compatible between the two, so for now we're not - // using IE logic for safari 3.1 - - // Weird special large keycode numbers for safari 3.0, where it gives - // us keypress events (though they happen after the dom is changed, - // for some things like delete. So not too useful). The number - // 63200 is known as the cutoff mark. - if (typeInt == Event.ONKEYDOWN && computedKeyCode > 63200) { - result.type = null; - return; - } else if (typeInt == Event.ONKEYPRESS) { - // Skip keypress for tab and escape, because they are the only non-input keys - // that don't have keycodes above 63200. This is to prevent them from being treated - // as INPUT in the || = keypress below. See (X) below - if (computedKeyCode == KeyCodes.KEY_ESCAPE - || computedKeyCode == KeyCodes.KEY_TAB) { - result.type = null; - return; - } - } - - // boolean isPossiblyCtrlInput = typeInt == Event.ONKEYDOWN && ret.getCtrlKey(); - boolean isActuallyCtrlInput = false; - - boolean startsWithUPlus = keyIdentifier != null && keyIdentifier.startsWith("U+"); - - // Need to use identifier for the delete key because the keycode conflicts - // with the keycode for the full stop. - if (isIME) { - // If is IME, override the logic below - we get keyIdentifiers for IME events, - // but those are basically useless as the event is basically still an IME input - // event (e.g. keyIdentifier might say "Up", but it's certainly not navigation, - // it's just the user selecting from the IME dialog). - type = KeySignalType.INPUT; - } else if (DELETE_KEY_IDENTIFIER.equals(keyIdentifier) || - computedKeyCode == KeyCodes.KEY_BACKSPACE) { - - type = KeySignalType.DELETE; - } else if (NAVIGATION_KEY_IDENTIFIERS.containsKey(keyIdentifier)) { - type = KeySignalType.NAVIGATION; - // Escape, backspace and context-menu-key (U+0010) are, to my knowledge, - // the only non-navigation keys that - // have a "U+..." keyIdentifier, so we handle them explicitly. - // (Backspace was handled earlier). - } else if (computedKeyCode == KeyCodes.KEY_ESCAPE || "U+0010".equals(keyIdentifier)) { - type = KeySignalType.NOEFFECT; - } else if ( - computedKeyCode < 63200 && // if it's not a safari 3.0 non-input key (See (X) above) - (typeInt == Event.ONKEYPRESS || // if it's a regular keypress - startsWithUPlus || computedKeyCode == KeyCodes.KEY_ENTER)) { - type = KeySignalType.INPUT; - isActuallyCtrlInput = ctrlKey - || (commandComboDoesntGiveKeypress && commandKey); - } else { - type = KeySignalType.NOEFFECT; - } - - // Maybe nullify it with the same logic as IE, EXCEPT for the special - // Ctrl Input webkit behaviour, and IME for windows - if (isActuallyCtrlInput) { - if (computedKeyCode == KeyCodes.KEY_ENTER) { - ret = typeInt == Event.ONKEYDOWN; - } - // HACK(danilatos): Don't actually nullify isActuallyCtrlInput for key press. - // We get that for AltGr combos on non-mac computers. - } else if (isIME || keyCode == KeyCodes.KEY_TAB) { - ret = typeInt == Event.ONKEYDOWN; - } else { - ret = maybeNullWebkitIE(ret, typeInt, type); - } - if (!ret) { - result.type = null; - return; - } - break; - case GECKO: - boolean hasKeyCodeButNotWhich = keyCode != 0 && which == 0; - - // Firefox is easy for deciding signal events, because it issues a keypress for - // whenever we would want a signal. So we can basically ignore all keydown events. - // It also, on all OSes, does any default action AFTER the keypress (even for - // things like Ctrl/Meta+C, etc). So keypress is perfect for us. - // Ctrl+Space is an exception, where we don't get a keypress - // Firefox also gives us keypress events even for Windows IME input - if (ctrlKey && !altKey && !shiftKey && computedKeyCode == ' ') { - if (typeInt != Event.ONKEYDOWN) { - result.type = null; - return; - } - } else if (typeInt == Event.ONKEYDOWN) { - result.type = null; - return; - } - - // Backspace fails the !hasKeyCodeButNotWhich test, so check it explicitly first - if (computedKeyCode == KeyCodes.KEY_BACKSPACE) { - type = KeySignalType.DELETE; - // This 'keyCode' but not 'which' works very nicely for catching normal typing input keys, - // the only 'exceptions' I've seen so far are bksp & enter which have both - } else if (!hasKeyCodeButNotWhich || computedKeyCode == KeyCodes.KEY_ENTER - || computedKeyCode == KeyCodes.KEY_TAB) { - type = KeySignalType.INPUT; - } else if (computedKeyCode == KeyCodes.KEY_DELETE) { - type = KeySignalType.DELETE; - } else if (NAVIGATION_KEYS.contains(computedKeyCode)) { - type = KeySignalType.NAVIGATION; - } else { - type = KeySignalType.NOEFFECT; - } - - break; - case IE: - - // Unfortunately IE gives us the least information, so there are no nifty tricks. - // So we pretty much need to use some educated guessing based on key codes. - // Experimentation page to the rescue. - - boolean isKeydownForInputKey = isInputKeyCodeIE(computedKeyCode); - - // IE has some strange behaviour with modifiers and whether or not there will - // be a keypress. Ctrl kills the keypress, unless shift is also held. - // Meta doesn't kill it. Alt always kills the keypress, overriding other rules. - boolean hasModifiersThatResultInNoKeyPress = - altKey || (ctrlKey && !shiftKey); - - if (typeInt == Event.ONKEYDOWN) { - if (isKeydownForInputKey) { - type = KeySignalType.INPUT; - } else if (computedKeyCode == KeyCodes.KEY_BACKSPACE || - computedKeyCode == KeyCodes.KEY_DELETE) { - type = KeySignalType.DELETE; - } else if (NAVIGATION_KEYS.contains(computedKeyCode)) { - type = KeySignalType.NAVIGATION; - } else { - type = KeySignalType.NOEFFECT; - } - } else { - // Escape is the only non-input thing that has a keypress event - if (computedKeyCode == KeyCodes.KEY_ESCAPE) { - result.type = null; - return; - } - assert typeInt == Event.ONKEYPRESS; - // I think the guessCommandFromModifiers() check here isn't needed, - // but i feel safer putting it in. - type = KeySignalType.INPUT; - } - - if (hasModifiersThatResultInNoKeyPress || isIME || computedKeyCode == KeyCodes.KEY_TAB) { - ret = typeInt == Event.ONKEYDOWN ? ret : false; - } else { - ret = maybeNullWebkitIE(ret, typeInt, type); - } - if (!ret) { - result.type = null; - return; - } - break; - default: - throw new UnsupportedOperationException("Unhandled user agent"); - } - - if (ret) { - result.type = type; - result.keyCode = computedKeyCode; - } else { - result.type = null; - return; - } - } - - private static final boolean isInputKeyCodeIE(int keyCode) { - /* - DATA - ---- - For KEYDOWN: - - "Input" - 48-57 (numbers) - 65-90 (a-z) - 96-111 (Numpad digits & other keys, with numlock off. with numlock on, they - behave like their corresponding keys on the rest of the keyboard) - 186-192 219-222 (random non-alphanumeric next to letters on RHS + backtick) - 229 Code that the input has passed to an IME - - Non-"input" - < 48 ('0') - 91-93 (Left & Right Win keys, ContextMenu key) - 112-123 (F1-F12) - 144-5 (NUMLOCK,SCROLL LOCK) - - For KEYPRESS: only "input" things get this event! yay! not even backspace! - Well, one exception: ESCAPE - */ - // boundaries in keycode ranges where the keycode for a keydown is for an input - // key. at "ON" it is, starting from the number going up, and the opposite for "OFF". - final int A_ON = 48; - final int B_OFF = 91; - final int C_ON = 96; - final int D_OFF = 112; - final int E_ON = 186; - - return - (keyCode == 9 || keyCode == 32 || keyCode == 13) || // And tab, enter & spacebar, of course! - (keyCode >= A_ON && keyCode < B_OFF) || - (keyCode >= C_ON && keyCode < D_OFF) || - (keyCode >= E_ON); - } - - /** - * Common logic between Webkit and IE for deciding whether we want the keydown - * or the keypress - */ - private static boolean maybeNullWebkitIE(boolean ret, int typeInt, - KeySignalType type) { - // Use keydown as the signal for everything except input. - // This is because the mutation always happens after the keypress for - // input (this is especially important for chrome, - // which interleaves deferred commands between keydown and keypress). - // - // For everything else, keypress is redundant with keydown, and also, the resulting default - // dom mutation (if any) often happens after the keydown but before the keypress in webkit. - // Also, if the 'Command' key is held for chrome/safari etc, we want to get the keydown - // event, NOT the keypress event, for everything because of things like ctrl+c etc. - // where sometimes it'll happen just after the keydown, or sometimes we just won't - // get a keypress at all - if (typeInt == (type == KeySignalType.INPUT ? Event.ONKEYDOWN : Event.ONKEYPRESS)) { - return false; - } - - return ret; - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/SimpleDoubleToIntMap.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SimpleDoubleToIntMap.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/SimpleDoubleToIntMap.java deleted file mode 100644 index 0566bfc..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/SimpleDoubleToIntMap.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - - -/** - * Cut down map where the keys are unboxed doubles and the values are unboxed ints - * - * @author danila...@google.com (Daniel Danilatos) - */ -public final class SimpleDoubleToIntMap extends JsoMapBase { - - protected SimpleDoubleToIntMap() {} - - /** Construct an empty SimpleDoubleMap */ - public static native SimpleDoubleToIntMap create() /*-{ - return {}; - }-*/; - - /** - * @param key - * @return true if a value indexed by the given key is in the map - */ - public native boolean has(double key) /*-{ - return this[key] !== undefined; - }-*/; - - /** - * @param key - * @return The value with the given key, or null if not present - */ - public native int get(double key) /*-{ - return this[key]; - }-*/; - - /** - * Put the value in the map at the given key. Note: Does not return the old - * value. - * - * @param key - * @param value - */ - public native void put(double key, int value) /*-{ - this[key] = value; - }-*/; - - /** - * Remove the value with the given key from the map. Note: does not return the - * old value. - * - * @param key - */ - public native void remove(double key) /*-{ - delete this[key]; - }-*/; -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringCodec.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringCodec.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringCodec.java deleted file mode 100644 index aa6e7a0..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringCodec.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.gwt.core.client.GWT; - - -/** - * Provides cross-platform encoding and decoding of arbitrary strings. - * - */ -public interface StringCodec { - /** - * A basic codec that moves the string to an alphabet that does not include - * commas. Decoding restores the original string. This allows commas to be - * used to mark structure (like Godel numbering). - */ - public StringCodec INSTANCE = GWT.isScript() ? new JsCodec() : new JavaCodec(); - - /** - * Encodes a string. This method provides the contract that, for any input - * string: - * <ul> - * <li>decode(encode(s)) == s; and</li> - * <li>encode(s) contains no characters from the set free()</li> - * </ul> - * - * @param s string to encode - * @return encoding of {@code s} - */ - String encode(String s); - - /** - * Decodes a string encoded by {@link #encode(String)}. - * - * @param s string to decode - * @return decoding of {@code s} - */ - String decode(String s); - - /** The characters that are removed from the alphabet encoded strings. */ - String free(); - - // - // Default implementations below. - // - // Simple quotation logic: - // 1. & --> && (promotes & as the quote char) - // 2. , --> &c (removes commas from alphabet) - // - // Unquotation is the inverse: - // 1. &c --> , (reinserts commas into alphabet) - // 2. && --> & (demotes & from being the quote char) - // - // - - final class JsCodec implements StringCodec { - @Override - public String free() { - return ","; - } - - @Override - public native String encode(String s) /*-{ - return s.replace(/&/g, "&&").replace(/,/g, "&c"); - }-*/; - - @Override - public native String decode(String s) /*-{ - return s.replace(/&c/g, ",").replace(/&&/g, "&"); - }-*/; - } - - final class JavaCodec implements StringCodec { - @Override - public String free() { - return ","; - } - - @Override - public String encode(String s) { - return s.replace("&", "&&").replace(",", "&c"); - } - - @Override - public String decode(String s) { - return s.replace("&c", ",").replace("&&", "&"); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringSequence.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringSequence.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringSequence.java deleted file mode 100644 index 03785c2..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringSequence.java +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; - -import java.util.Collection; - -/** - * Encodes a string sequence as a single string. As per the {@link Sequence} - * interface, repeated strings are not supported. - * <p> - * The single string that holds the sequences is a comma-delimited list. There - * are no restrictions on the strings that can be placed in this sequence - - * strings are encoded and decoded appropriately. For example, - * - * <pre> - * ["foo", "bar"] --> ",foo,bar," - * ["foo,bar", "foo&baz"] --> ",foo&,bar,foo&&baz," - * </pre> - * - * All random-access methods in this implementation are linear time, so a linear - * number of accesses is quadratic in the worst case. This class expects a - * sequential access pattern, and is implemented so that <em>m</em> sequential - * queries on an <em>n</em>-sized sequence is only <em>O(n + m)</em>. This is - * not true for mutations, however: a series of <em>m</em> mutations, sequential - * or not, will be <em>O(nm)</em>. For a sequence implementation that provides - * expected constant time complexity for all methods, see {@link LinkedSequence}. - * - */ -// -// Unescaped version of the example above: -// ["foo", "bar"] --> ",foo,bar," -// ["foo,bar", "baz&quux"] --> ",foo&cbar,baz&&quux," -// -public final class StringSequence implements Sequence<String> { - /** Codec to use for encoding / decoding values in the list. */ - private static final StringCodec CODEC = StringCodec.INSTANCE; - - /** String to demarcate items in the list. */ - private static final String DELIMITER = CODEC.free().substring(0, 1); - - /** - * Embedded list. Never null, and is always is of the form: DELIMITER - * (NONDELIMITER+ DELIMITER)*. - */ - // Note: composite linear complexity for sequential writes (i.e., m sequential - // writes is only O(n + m)) could be achieved by alternating between a - // serialized string and a stringbuffer of pending sequential mutations. This - // is not implemented in order to keep this implementation very lightweight. - private String data; - - /** - * Last index used in a reference search. This is used to optimize for - * sequential access, so that m queries cost (n + m) rather than O(nm). - */ - private int recentIndex; - - @VisibleForTesting - StringSequence(String data) { - this.data = data; - } - - /** Creates an empty string sequence. */ - public static StringSequence create() { - return new StringSequence(DELIMITER); - } - - /** Creates a string sequence on a string from another {@code StringSequence}. */ - public static StringSequence create(String serializedSequence) { - Preconditions.checkArgument(serializedSequence.startsWith(DELIMITER) - && serializedSequence.endsWith(DELIMITER)); - return new StringSequence(serializedSequence); - } - - /** Creates a string sequence with an initial state. */ - public static StringSequence of(Collection<String> xs) { - StringBuilder data = new StringBuilder(); - data.append(DELIMITER); - for (String x : xs) { - data.append(CODEC.encode(x)); - data.append(DELIMITER); - } - return new StringSequence(data.toString()); - } - - // JS does not do automatic builderization of composite concatentaions. - /** Concatenates strings. */ - private static String concat(String s1, String s2, String s3) { - StringBuilder s = new StringBuilder(); - s.append(s1); - s.append(s2); - s.append(s3); - return s.toString(); - } - - /** Concatenates strings. */ - private static String concat(String s1, String s2, String s3, String s4) { - StringBuilder s = new StringBuilder(); - s.append(s1); - s.append(s2); - s.append(s3); - s.append(s4); - return s.toString(); - } - - @Override - public boolean contains(String x) { - return data.contains(concat(DELIMITER, CODEC.encode(x), DELIMITER)); - } - - /** - * Finds a term in the data string. The search is initiated from the location - * of the most recent hit. - * - * @param term (coded) term to search for - * @param forwardFirst if true, the search is performed with a forward search - * then a backward search; if false, the search is performed with a - * backward search then a forward search; - * @return the index after {@code term} for a forward search; the index of - * {@code} term for a backward search. - * @throws IllegalArgumentException if {@code term} is not found. - */ - private int find(String term, boolean forwardFirst) { - int index; - - if (forwardFirst) { - // Search forward, leaving cursor after the find. - index = data.indexOf(term, recentIndex); - if (index >= 0) { - return recentIndex = index + term.length(); - } - - // Search backward, leaving cursor after the find. - index = data.lastIndexOf(term, recentIndex); - if (index >= 0) { - return recentIndex = index + term.length(); - } - } else { - // Search backward, leaving cursor before the find. - index = data.lastIndexOf(term, recentIndex); - if (index >= 0) { - return recentIndex = index; - } - - // Search forward, leaving cursor before the find. - index = data.indexOf(term, recentIndex); - if (index >= 0) { - return recentIndex = index; - } - } - - // Miss. - throw new IllegalArgumentException("Item not found: " - + CODEC.decode(term.substring(DELIMITER.length(), term.length() - DELIMITER.length()))); - } - - private int findForward(String term) { - return find(term, true); - } - - private int findBackward(String term) { - return find(term, false); - } - - @Override - public String getFirst() { - // - // , f o o , ... , b a r , - // . ^ first - // - recentIndex = DELIMITER.length(); - return recentIndex == data.length() ? null // \u2620 - : CODEC.decode(data.substring(recentIndex, data.indexOf(DELIMITER, recentIndex))); - } - - @Override - public String getLast() { - // - // , f o o , ... , b a r , - // 0 1 . . . ... . . . . ^ last - // - recentIndex = data.length() - DELIMITER.length(); - return recentIndex == 0 ? null // \u2620 - : CODEC.decode(data.substring(data.lastIndexOf(DELIMITER, recentIndex - 1) - + DELIMITER.length(), recentIndex)); - } - - @Override - public String getNext(String x) { - if (x == null) { - return getFirst(); - } - // - // if x = "foobar", then - // , ... , f o o b a r , b a z q u u x , ... , - // . . . ^ index . . . . ^ nextStart . ^ nextEnd - // - String coded = concat(DELIMITER, CODEC.encode(x), DELIMITER); - int nextStart = findForward(coded); - if (nextStart == data.length()) { - return null; - } else { - int nextEnd = data.indexOf(DELIMITER, nextStart); - return CODEC.decode(data.substring(nextStart, nextEnd)); - } - } - - @Override - public String getPrevious(String x) { - if (x == null) { - return getLast(); - } - // - // if x = "foobar", then - // , ... , b a z q u u x , f o o b a r , ... , - // . . . . ^ prevStart . ^ index / prevEnd - // - String coded = concat(DELIMITER, CODEC.encode(x), DELIMITER); - int prevEnd = findBackward(coded); - if (prevEnd == 0) { - return null; - } else { - int prevStart = data.lastIndexOf(DELIMITER, prevEnd - 1) + DELIMITER.length(); - return CODEC.decode(data.substring(prevStart, prevEnd)); - } - } - - @Override - public boolean isEmpty() { - // Being shorter than DELIMITER is impossible, due to data's invariant. - return data.length() == DELIMITER.length(); - } - - /** - * Inserts a value before a reference item. - * - * @param ref reference value (or {@code null} for append) - * @param x value to insert - * @throws IllegalArgumentException if {@code x} is null, or {@code ref} is - * non-null and not in this sequence. - */ - public void insertBefore(String ref, String x) { - Preconditions.checkArgument(x != null, "null item"); - if (ref == null) { - data += CODEC.encode(x) + DELIMITER; - } else { - // if ref = "foobar", then - // , ... , b a z q u u x , f o o b a r , ... , - // . . . . . . . . . . . ^ refIndex - String codedRef = concat(DELIMITER, CODEC.encode(ref), DELIMITER); - int refIndex = findBackward(codedRef); - data = - concat(data.substring(0, refIndex), DELIMITER, CODEC.encode(x), data.substring(refIndex)); - } - } - - /** - * Inserts a value after a reference item. - * - * @param ref reference value (or {@code null} for prepend) - * @param x value to insert - * @throws IllegalArgumentException if {@code x} is null, or {@code ref} is - * non-null and not in this sequence. - */ - public void insertAfter(String ref, String x) { - Preconditions.checkArgument(x != null, "null item"); - if (ref == null) { - data = DELIMITER + CODEC.encode(x) + data; - } else { - // if ref = "foobar", then - // , ... , f o o b a r , b a z q u u x , ... , - // . . . ^refIndex . . . ^refIndex' - String codedRef = concat(DELIMITER, CODEC.encode(ref), DELIMITER); - int refIndex = findForward(codedRef); - data = - concat(data.substring(0, refIndex), CODEC.encode(x), DELIMITER, data.substring(refIndex)); - } - } - - /** - * Removes a value from this sequence. - * - * @param x - * @throws IllegalArgumentException if {@code x} is null or not in this - * sequence. - */ - public void remove(String x) { - // if ref = "foobar", then - // , ... , b a r , f o o , b a z , ... , - // . . . . . . . ^refIndex - Preconditions.checkArgument(x != null, "null item"); - String coded = concat(DELIMITER, CODEC.encode(x), DELIMITER); - int index = findBackward(coded); - data = data.substring(0, index) + data.substring(index + coded.length() - DELIMITER.length()); - // Reminder that the trailing delimiter is there. - assert data.startsWith(DELIMITER, index); - } - - /** - * Clears all entries from this sequence. - */ - public void clear() { - data = DELIMITER; - recentIndex = 0; - } - - /** @return the underyling data in which strings are embedded. */ - public String getRaw() { - return data; - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringUtil.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringUtil.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringUtil.java deleted file mode 100644 index 933d88a..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/StringUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -/** - * String functions. - * - */ -public class StringUtil { - - /** - * @param xml - * @return XML-escaped string - */ - public static String xmlEscape(String xml) { - return xml - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">"); - } - - /** - * Notice that this function only escape entity reference and not character reference. - * @param xml - * @return the unescaped xml string. - */ - public static String xmlUnescape(String xml) { - return xml - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll(""", "\"") - .replaceAll("'", "'") - .replaceAll("&", "&"); - } -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgent.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgent.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgent.java deleted file mode 100644 index 90e469c..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgent.java +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - - -/** - * Information about the current user agent. Some of the information is - * dynamically determined at runtime, other information is determined at compile - * time as it is baked into the particular permutation with the deferred binding - * mechanism. - * - * Methods that are statically evaluable allow conditional compilation for - * different user agents. - * - * e.g. currently, the following code: - * - * if (UserAgent.isIE()) { - * // do IE-specific implementation - * } else { - * // do non-IE implementation - * } - * - * should for "user.agent" set to "ie6" compile down to just the IE-specific - * implementation. - * - * It is not exposed as part of this API which methods are statically determined - * and which are not, as this may be subject to change. In general it should not - * matter as the cost of a runtime check is very cheap. If it does matter, it is - * up to the caller to know and keep track of the current state of affairs. - * - */ -public abstract class UserAgent { - - /** - * @return true iff the user agent uses webkit - */ - public static boolean isWebkit() { - return UserAgentStaticProperties.get().isWebkit(); - } - - /** - * @return true iff the user agent uses mobile webkit - */ - public static boolean isMobileWebkit() { - return UserAgentStaticProperties.get().isMobileWebkit(); - } - - /** - * @return true iff the user.agent GWT property is "safari" - */ - public static boolean isSafari() { - return UserAgentStaticProperties.get().isSafari(); - } - - /** - * @return true iff the user.agent GWT property is "gecko" or "gecko1_8" - */ - public static boolean isFirefox() { - return UserAgentStaticProperties.get().isFirefox(); - } - - /** - * @return true iff the user.agent GWT property is "ie6" - */ - public static boolean isIE() { - return UserAgentStaticProperties.get().isIE(); - } - - /** - * @return true iff the user.agent GWT property is "android" - */ - public static boolean isAndroid() { - return UserAgentStaticProperties.get().isAndroid(); - } - - /** - * @return true iff the user.agent GWT property is "iphone" - */ - public static boolean isIPhone() { - return UserAgentStaticProperties.get().isIPhone(); - } - - /** - * @return true if this is the chrome browser - */ - public static boolean isChrome() { - return UserAgentRuntimeProperties.get().isChrome(); - } - - public static boolean isIE7() { - return UserAgentRuntimeProperties.get().isIe7(); - } - - public static boolean isIE8() { - return UserAgentRuntimeProperties.get().isIe8(); - } - - /** - * @return true if we are on OSX - */ - public static boolean isMac() { - return UserAgentRuntimeProperties.get().isMac(); - } - - /** - * @return true if we are on Windows - */ - public static boolean isWin() { - return UserAgentRuntimeProperties.get().isWin(); - } - - /** - * @return true if we are on Linux - */ - public static boolean isLinux() { - return UserAgentRuntimeProperties.get().isLinux(); - } - - /** - * Debug method that returns the user-agent string. - * - * NOTE(user): FOR DEBUGGING PURPOSES ONLY. DO NOT USE FOR PROGRAM LOGIC. - */ - public static String debugUserAgentString() { - return UserAgentRuntimeProperties.get().getUserAgent(); - } - - /** - * @return whether the current user agent version is at least the one given by - * the method parameters. - */ - public static boolean isAtLeastVersion(int major, int minor) { - return UserAgentRuntimeProperties.get().isAtLeastVersion(major, minor); - } - - /** - * Do not use this for program logic - for debugging only. For program logic, - * instead use {@link #isAtLeastVersion(int, int)} - */ - public static int debugGetMajorVer() { - return UserAgentRuntimeProperties.get().getMajorVer(); - } - - /** - * Do not use this for program logic - for debugging only. For program logic, - * instead use {@link #isAtLeastVersion(int, int)} - */ - public static int debugGetMinorVer() { - return UserAgentRuntimeProperties.get().getMinorVer(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/051db092/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgentRuntimeProperties.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgentRuntimeProperties.java b/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgentRuntimeProperties.java deleted file mode 100644 index 2cab1b5..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/client/common/util/UserAgentRuntimeProperties.java +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.waveprotocol.wave.client.common.util; - -import com.google.gwt.core.client.GWT; - -import org.waveprotocol.wave.model.util.Box; -import com.google.common.annotations.VisibleForTesting; - -/** - * Class to contain run-time checks of a user-agent's capabilities. - * - * - */ -@VisibleForTesting -public class UserAgentRuntimeProperties { - private static final UserAgentRuntimeProperties INSTANCE = createInstance(); - - private static UserAgentRuntimeProperties createInstance() { - return GWT.isClient() ? new UserAgentRuntimeProperties(getNativeUserAgent()) - : new UserAgentRuntimeProperties(""); - } - - @VisibleForTesting - public static UserAgentRuntimeProperties get() { - return INSTANCE; - } - - private final String userAgent; - private final int version; - private final boolean isMac; - private final boolean isWin; - private final boolean isLinux; - private final boolean isIe7; - private final boolean isIe8; - private final boolean isChrome; - - @VisibleForTesting - public UserAgentRuntimeProperties(String userAgent) { - this.userAgent = userAgent; - this.version = calculateVersion(userAgent); - this.isMac = calculateIsMac(userAgent); - this.isWin = calculateIsWin(userAgent); - this.isLinux = calculateIsLinux(userAgent); - this.isIe7 = calculateIe7(userAgent); - this.isIe8 = calculateIe8(userAgent); - this.isChrome = calculateIsChrome(userAgent); - } - - @VisibleForTesting - public String getUserAgent() { - return userAgent; - } - - @VisibleForTesting - public boolean isMac() { - return isMac; - } - - @VisibleForTesting - public boolean isWin() { - return isWin; - } - - @VisibleForTesting - public boolean isLinux() { - return isLinux; - } - - @VisibleForTesting - public boolean isIe7() { - return isIe7; - } - - @VisibleForTesting - public boolean isIe8(){ - return isIe8; - } - - @VisibleForTesting - public boolean isChrome() { - return isChrome; - } - - /** - * @return whether the current user agent version is at least the one given by - * the method parameters. - */ - @VisibleForTesting - public boolean isAtLeastVersion(int major, int minor) { - return version >= (major * 1000 + minor); - } - - /** - * Do not use this for program logic - for debugging only. For program logic, - * instead use {@link #isAtLeastVersion(int, int)} - */ - @VisibleForTesting - public int getMajorVer() { - return version / 1000; - } - - /** - * Do not use this for program logic - for debugging only. For program logic, - * instead use {@link #isAtLeastVersion(int, int)} - */ - @VisibleForTesting - public int getMinorVer() { - return version % 1000; - } - - private static native String getNativeUserAgent() /*-{ - return navigator.userAgent; - }-*/; - - private static boolean calculateIe7(String userAgent) { - return userAgent.indexOf(" MSIE 7.") != -1; - } - - private static boolean calculateIe8(String userAgent) { - return userAgent.indexOf(" MSIE 8.") != -1; - } - - private static boolean calculateIsMac(String userAgent) { - return userAgent.indexOf("Mac") != -1; - } - - private static boolean calculateIsWin(String userAgent) { - return userAgent.indexOf("Windows") != -1; - } - - private static boolean calculateIsLinux(String userAgent) { - return userAgent.indexOf("Linux") != -1; - } - - private static boolean calculateIsChrome(String userAgent) { - return userAgent.indexOf("Chrome") != -1; - } - - private static int calculateVersion(String userAgent) { - if (userAgent == null || userAgent.isEmpty()) { - return -1; - } - -// TODO(user): Make this work after regex deps are fixed and don't break static rendering -// -// String regexps[] = {"firefox.([0-9]+).([0-9]+)", -// "webkit.([0-9]+).([0-9]+)", -// "msie.([0-9]+).([0-9]+)", -// "minefield.([0-9]+).([0-9]+)"}; - - -// TODO(user): Don't use "firefox" and "minefield", check Gecko rv. - String names[] = {"firefox", "webkit", "msie", "minefield"}; - - for (String name : names) { - int v = calculateVersion(name, userAgent); - if (v >= 0) { - return v; - } - } - return -1; - } - -// /** -// * Matches a browser-specific regular expression against the user agent to -// * obtain a version number. -// * -// * @param regexp The browser-specific regular expression to use -// * @param userAgent The user agent string to check -// * @return A version number or -1 if unknown -// */ - /** - * Matches a browser-specific name against the user agent to obtain a version - * number. - * - * @param name The browser-specific name to use - * @param userAgent The user agent string to check - * @return A version number or -1 if unknown - */ - private static int calculateVersion(String name, String userAgent) { - int index = userAgent.toLowerCase().indexOf(name); - if (index == -1) { - return -1; - } - - Box<Integer> output = Box.create(); - - index += name.length() + 1; - - if ((index = consumeDigit(index, userAgent, output)) == -1) { - return -1; - } - int major = output.boxed; - - index++; - - if ((index = consumeDigit(index, userAgent, output)) == -1) { - return -1; - } - int minor = output.boxed; - - return major * 1000 + minor; - -// TODO(user): Make this work after regex deps are fixed and don't break static rendering -// -// RegExp pattern = RegExp.compile(regexp); -// MatchResult result = pattern.exec(userAgent.toLowerCase()); -// if (result != null && result.getGroupCount() == 3) { -// int major = Integer.parseInt(result.getGroup(1)); -// int minor = Integer.parseInt(result.getGroup(2)); -// return major * 1000 + minor; -// } -// return -1; - } - - private static int consumeDigit(int index, String str, Box<Integer> output) { - StringBuilder nb = new StringBuilder(); - - char c; - while (index < str.length() && Character.isDigit( (c = str.charAt(index)) )) { - nb.append(c); - index++; - } - - if (nb.length() == 0) { - return -1; - } - - try { - output.boxed = Integer.parseInt(nb.toString()); - return index; - } catch (NumberFormatException e) { - return -1; - } - } - -}